SlideShare a Scribd company logo
1 of 80
Download to read offline
Typical pitfalls
of Sylius development
Thank you!
Why do we do this?
😂😂😂
AI is not a threat for now
Q&A
Performance
N+1
Full stack mode
Headless mode
Product list
Let’s start small
{
"@context":"/api/contexts/Product",
"@id":"/api/products",
"@type":"hydra:Collection",
"hydra:totalItems":6,
"hydra:member":[
{
"@id":"/api/products/1",
"@type":"Product",
"id":1,
"name":"T-Shirt",
"price":1000
},
{
"@id":"/api/products/2",
"@type":"Product",
"id":2,
"name":"Trousers",
"price":5000
},
{
"“…“":"“…“"
}
]
}
Amount of products: 6
No associations between objects
Sample query on the left
O(1)
Order list
How many queries are executed here?
{
"@context": "/api/contexts/Order",
"@id": "/api/orders",
"@type": "hydra:Collection",
"hydra:totalItems": 3,
"hydra:member": [
{
"@id": "/api/orders/1",
"@type": "Order",
"id": 1,
"orderItems": [
"/api/order_items/1",
"/api/order_items/2"
]
},
{
“…”: “…“
}
]
}
Amount of orders: 3
Every order associated with 2 items
Sample query on the left
No total
fi
eld
O(M)
Order list with additional
fi
eld
How many queries are executed here?
{
"@context": "/api/contexts/Order",
"@id": "/api/orders",
"@type": "hydra:Collection",
"hydra:totalItems": 3,
"hydra:member": [
{
"@id": "/api/orders/1",
"@type": "Order",
"id": 1,
"orderItems": [
"/api/order_items/1",
"/api/order_items/2"
],
"total": 7000
},
{
“…”: “…“
}
]
}
Amount of orders: 3
Every order associated with 2 items
Sample query on the left
Added “total()” as a function of product
price and quantity of item
O(M*N) or O(M^2) 🚀
How to
fi
x it?
Solution 1
#[ORMOneToMany(fetch: ‘LAZY')] => 11
#[ORMOneToMany(fetch: ‘EXTRA_LAZY')] => 11
#[ORMOneToMany(fetch: ‘EAGER')] => 5 (OrderItem and Product)
#[ORMOneToMany(fetch: ‘EAGER')] (OrderItem and Product) + Serialisation groups => 4
Solution 2
final class LoadItemsAndProductsExtension implements QueryCollectionExtensionInterface,
QueryItemExtensionInterface
{
private function apply(QueryBuilder $queryBuilder): void
{
$queryBuilder
->addSelect('oi', 'p')
->join(OrderItem::class, 'oi', Join::WITH, 'oi.originOrder = o')
->join(Product::class, 'p', Join::WITH, 'oi.product = p')
;
}
}
But….
{
"@context": "/api/contexts/Order",
"@id": "/api/orders",
"@type": "hydra:Collection",
"hydra:totalItems": 3,
"hydra:member": [
{
"@id": "/api/orders/1",
"@type": "Order",
"id": 1,
"orderItems": [
"/api/order_items/1",
"/api/order_items/2"
],
"total": 7000
},
{
"@id": "/api/order_items/1",
"@type": "OrderItem",
"id": 1,
"product": "/api/products/1",
"quantity": 2,
"originOrder": "/api/orders/1",
"price": 2000
},
{
"@id": "/api/products/1",
"@type": "Product",
"id": 1,
"name": "T-Shirt",
"price": 1000
},
{
"@id": "/api/order_items/2",
"@type": "OrderItem",
"id": 2,
"product": "/api/products/2",
"quantity": 1,
"originOrder": "/api/orders/1",
"price": 5000
},
{
“…”: “…”
}
]
}
So is single query an ultimate
solution?
Concurrency from di
ff
erent
users
Sequential order number
generator
Does it need to be
sequential?
Or defer it 😉
Locking mechanism of
product variants (inventory)
What is locked?
Concurrency from the same
user
Order Processing
How does Doctrine collection
handling works?
Tests
Contract tests
Functional tests
You don’t need to do BDD
to work with Sylius
Although we ecourage you to do so
No. of Behat scenarios in our
latest successfull Sylius project:
0
No. of Behat scenarios in our
latest successfull Sylius project:
Testing documentation
Payments
Payments
Current
fl
ow
Payment after checkout
Typical request
Payment before completion
What we want to achieve
Authorization during checkout
Flexible capture moment
Core Team
is actively working on it
Resource
Resource
Regular Symfony
controllers
<?php
/*
* This
fi
le is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
*
fi
le that was distributed with this source code.
*/
declare(strict_types=1);
namespace SyliusBundleResourceBundleController;
use DoctrinePersistenceObjectManager;
use FOSRestBundleViewView;
use SyliusBundleResourceBundleEventResourceControllerEvent;
use SyliusComponentResourceExceptionDeleteHandlingException;
use SyliusComponentResourceExceptionUpdateHandlingException;
use SyliusComponentResourceFactoryFactoryInterface;
use SyliusComponentResourceMetadataMetadataInterface;
use SyliusComponentResourceModelResourceInterface;
use SyliusComponentResourceRepositoryRepositoryInterface;
use SyliusComponentResourceResourceActions;
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 RequestCon
fi
gurationFactoryInterface $requestCon
fi
gurationFactory;
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 $
fl
ashHelper;
protected AuthorizationCheckerInterface $authorizationChecker;
protected EventDispatcherInterface $eventDispatcher;
protected ?StateMachineInterface $stateMachine;
protected ResourceUpdateHandlerInterface $resourceUpdateHandler;
protected ResourceDeleteHandlerInterface $resourceDeleteHandler;
public function __construct(
MetadataInterface $metadata,
RequestCon
fi
gurationFactoryInterface $requestCon
fi
gurationFactory,
?ViewHandlerInterface $viewHandler,
RepositoryInterface $repository,
FactoryInterface $factory,
NewResourceFactoryInterface $newResourceFactory,
ObjectManager $manager,
SingleResourceProviderInterface $singleResourceProvider,
ResourcesCollectionProviderInterface $resourcesFinder,
ResourceFormFactoryInterface $resourceFormFactory,
RedirectHandlerInterface $redirectHandler,
FlashHelperInterface $
fl
ashHelper,
AuthorizationCheckerInterface $authorizationChecker,
EventDispatcherInterface $eventDispatcher,
?StateMachineInterface $stateMachine,
ResourceUpdateHandlerInterface $resourceUpdateHandler,
ResourceDeleteHandlerInterface $resourceDeleteHandler,
) {
$this->metadata = $metadata;
$this->requestCon
fi
gurationFactory = $requestCon
fi
gurationFactory;
$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->
fl
ashHelper = $
fl
ashHelper;
$this->authorizationChecker = $authorizationChecker;
$this->eventDispatcher = $eventDispatcher;
$this->stateMachine = $stateMachine;
$this->resourceUpdateHandler = $resourceUpdateHandler;
$this->resourceDeleteHandler = $resourceDeleteHandler;
}
public function showAction(Request $request): Response
{
$con
fi
guration = $this->requestCon
fi
gurationFactory->create($this->metadata, $request);
$this->isGrantedOr403($con
fi
guration, ResourceActions::SHOW);
$resource = $this->
fi
ndOr404($con
fi
guration);
$event = $this->eventDispatcher->dispatch(ResourceActions::SHOW, $con
fi
guration, $resource);
$eventResponse = $event->getResponse();
if (null !== $eventResponse) {
return $eventResponse;
}
if ($con
fi
guration->isHtmlRequest()) {
return $this->render($con
fi
guration->getTemplate(ResourceActions::SHOW . '.html'), [
'con
fi
guration' => $con
fi
guration,
'metadata' => $this->metadata,
'resource' => $resource,
$this->metadata->getName() => $resource,
]);
}
return $this->createRestView($con
fi
guration, $resource);
}
public function indexAction(Request $request): Response
{
$con
fi
guration = $this->requestCon
fi
gurationFactory->create($this->metadata, $request);
$this->isGrantedOr403($con
fi
guration, ResourceActions::INDEX);
$resources = $this->resourcesCollectionProvider->get($con
fi
guration, $this->repository);
$event = $this->eventDispatcher->dispatchMultiple(ResourceActions::INDEX, $con
fi
guration, $resources);
$eventResponse = $event->getResponse();
if (null !== $eventResponse) {
return $eventResponse;
}
if ($con
fi
guration->isHtmlRequest()) {
return $this->render($con
fi
guration->getTemplate(ResourceActions::INDEX . '.html'), [
'con
fi
guration' => $con
fi
guration,
'metadata' => $this->metadata,
'resources' => $resources,
$this->metadata->getPluralName() => $resources,
]);
}
return $this->createRestView($con
fi
guration, $resources);
}
public function createAction(Request $request): Response
{
$con
fi
guration = $this->requestCon
fi
gurationFactory->create($this->metadata, $request);
$this->isGrantedOr403($con
fi
guration, ResourceActions::CREATE);
$newResource = $this->newResourceFactory->create($con
fi
guration, $this->factory);
$form = $this->resourceFormFactory->create($con
fi
guration, $newResource);
$form->handleRequest($request);
if ($request->isMethod('POST') && $form->isSubmitted() && $form->isValid()) {
$newResource = $form->getData();
$event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::CREATE, $con
fi
guration, $newResource);
if ($event->isStopped() && !$con
fi
guration->isHtmlRequest()) {
throw new HttpException($event->getErrorCode(), $event->getMessage());
}
if ($event->isStopped()) {
$this->
fl
ashHelper->addFlashFromEvent($con
fi
guration, $event);
$eventResponse = $event->getResponse();
if (null !== $eventResponse) {
return $eventResponse;
}
return $this->redirectHandler->redirectToIndex($con
fi
guration, $newResource);
}
if ($con
fi
guration->hasStateMachine()) {
$stateMachine = $this->getStateMachine();
$stateMachine->apply($con
fi
guration, $newResource);
}
$this->repository->add($newResource);
if ($con
fi
guration->isHtmlRequest()) {
$this->
fl
ashHelper->addSuccessFlash($con
fi
guration, ResourceActions::CREATE, $newResource);
}
$postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::CREATE, $con
fi
guration, $newResource);
if (!$con
fi
guration->isHtmlRequest()) {
return $this->createRestView($con
fi
guration, $newResource, Response::HTTP_CREATED);
}
$postEventResponse = $postEvent->getResponse();
if (null !== $postEventResponse) {
return $postEventResponse;
}
return $this->redirectHandler->redirectToResource($con
fi
guration, $newResource);
}
if ($request->isMethod('POST') && $form->isSubmitted() && !$form->isValid()) {
$responseCode = Response::HTTP_UNPROCESSABLE_ENTITY;
}
if (!$con
fi
guration->isHtmlRequest()) {
return $this->createRestView($con
fi
guration, $form, Response::HTTP_BAD_REQUEST);
}
$initializeEvent = $this->eventDispatcher->dispatchInitializeEvent(ResourceActions::CREATE, $con
fi
guration, $newResource);
$initializeEventResponse = $initializeEvent->getResponse();
if (null !== $initializeEventResponse) {
return $initializeEventResponse;
}
return $this->render($con
fi
guration->getTemplate(ResourceActions::CREATE . '.html'), [
'con
fi
guration' => $con
fi
guration,
'metadata' => $this->metadata,
'resource' => $newResource,
$this->metadata->getName() => $newResource,
'form' => $form->createView(),
], null, $responseCode ?? Response::HTTP_OK);
}
public function updateAction(Request $request): Response
{
$con
fi
guration = $this->requestCon
fi
gurationFactory->create($this->metadata, $request);
$this->isGrantedOr403($con
fi
guration, ResourceActions::UPDATE);
$resource = $this->
fi
ndOr404($con
fi
guration);
$form = $this->resourceFormFactory->create($con
fi
guration, $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, $con
fi
guration, $resource);
if ($event->isStopped() && !$con
fi
guration->isHtmlRequest()) {
throw new HttpException($event->getErrorCode(), $event->getMessage());
}
if ($event->isStopped()) {
$this->
fl
ashHelper->addFlashFromEvent($con
fi
guration, $event);
$eventResponse = $event->getResponse();
if (null !== $eventResponse) {
return $eventResponse;
}
return $this->redirectHandler->redirectToResource($con
fi
guration, $resource);
}
try {
$this->resourceUpdateHandler->handle($resource, $con
fi
guration, $this->manager);
} catch (UpdateHandlingException $exception) {
if (!$con
fi
guration->isHtmlRequest()) {
return $this->createRestView($con
fi
guration, $form, $exception->getApiResponseCode());
}
$this->
fl
ashHelper->addErrorFlash($con
fi
guration, $exception->getFlash());
return $this->redirectHandler->redirectToReferer($con
fi
guration);
}
if ($con
fi
guration->isHtmlRequest()) {
$this->
fl
ashHelper->addSuccessFlash($con
fi
guration, ResourceActions::UPDATE, $resource);
}
$postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::UPDATE, $con
fi
guration, $resource);
if (!$con
fi
guration->isHtmlRequest()) {
if ($con
fi
guration->getParameters()->get('return_content', false)) {
return $this->createRestView($con
fi
guration, $resource, Response::HTTP_OK);
}
return $this->createRestView($con
fi
guration, null, Response::HTTP_NO_CONTENT);
}
$postEventResponse = $postEvent->getResponse();
if (null !== $postEventResponse) {
return $postEventResponse;
}
return $this->redirectHandler->redirectToResource($con
fi
guration, $resource);
}
if (in_array($request->getMethod(), ['POST', 'PUT', 'PATCH'], true) && $form->isSubmitted() && !$form->isValid()) {
$responseCode = Response::HTTP_UNPROCESSABLE_ENTITY;
}
if (!$con
fi
guration->isHtmlRequest()) {
return $this->createRestView($con
fi
guration, $form, Response::HTTP_BAD_REQUEST);
}
$initializeEvent = $this->eventDispatcher->dispatchInitializeEvent(ResourceActions::UPDATE, $con
fi
guration, $resource);
$initializeEventResponse = $initializeEvent->getResponse();
if (null !== $initializeEventResponse) {
return $initializeEventResponse;
}
return $this->render($con
fi
guration->getTemplate(ResourceActions::UPDATE . '.html'), [
'con
fi
guration' => $con
fi
guration,
'metadata' => $this->metadata,
'resource' => $resource,
$this->metadata->getName() => $resource,
'form' => $form->createView(),
], null, $responseCode ?? Response::HTTP_OK);
}
public function deleteAction(Request $request): Response
{
$con
fi
guration = $this->requestCon
fi
gurationFactory->create($this->metadata, $request);
$this->isGrantedOr403($con
fi
guration, ResourceActions::DELETE);
$resource = $this->
fi
ndOr404($con
fi
guration);
if ($con
fi
guration->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, $con
fi
guration, $resource);
if ($event->isStopped() && !$con
fi
guration->isHtmlRequest()) {
throw new HttpException($event->getErrorCode(), $event->getMessage());
}
if ($event->isStopped()) {
$this->
fl
ashHelper->addFlashFromEvent($con
fi
guration, $event);
$eventResponse = $event->getResponse();
if (null !== $eventResponse) {
return $eventResponse;
}
return $this->redirectHandler->redirectToIndex($con
fi
guration, $resource);
}
try {
$this->resourceDeleteHandler->handle($resource, $this->repository);
} catch (DeleteHandlingException $exception) {
if (!$con
fi
guration->isHtmlRequest()) {
return $this->createRestView($con
fi
guration, null, $exception->getApiResponseCode());
}
$this->
fl
ashHelper->addErrorFlash($con
fi
guration, $exception->getFlash());
return $this->redirectHandler->redirectToReferer($con
fi
guration);
}
if ($con
fi
guration->isHtmlRequest()) {
$this->
fl
ashHelper->addSuccessFlash($con
fi
guration, ResourceActions::DELETE, $resource);
}
$postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::DELETE, $con
fi
guration, $resource);
if (!$con
fi
guration->isHtmlRequest()) {
return $this->createRestView($con
fi
guration, null, Response::HTTP_NO_CONTENT);
}
$postEventResponse = $postEvent->getResponse();
if (null !== $postEventResponse) {
return $postEventResponse;
}
return $this->redirectHandler->redirectToIndex($con
fi
guration, $resource);
}
public function bulkDeleteAction(Request $request): Response
{
$con
fi
guration = $this->requestCon
fi
gurationFactory->create($this->metadata, $request);
$this->isGrantedOr403($con
fi
guration, ResourceActions::BULK_DELETE);
$resources = $this->resourcesCollectionProvider->get($con
fi
guration, $this->repository);
if (
$con
fi
guration->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, $con
fi
guration, $resources);
foreach ($resources as $resource) {
$event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::DELETE, $con
fi
guration, $resource);
if ($event->isStopped() && !$con
fi
guration->isHtmlRequest()) {
throw new HttpException($event->getErrorCode(), $event->getMessage());
}
if ($event->isStopped()) {
$this->
fl
ashHelper->addFlashFromEvent($con
fi
guration, $event);
$eventResponse = $event->getResponse();
if (null !== $eventResponse) {
return $eventResponse;
}
return $this->redirectHandler->redirectToIndex($con
fi
guration, $resource);
}
try {
$this->resourceDeleteHandler->handle($resource, $this->repository);
} catch (DeleteHandlingException $exception) {
if (!$con
fi
guration->isHtmlRequest()) {
return $this->createRestView($con
fi
guration, null, $exception->getApiResponseCode());
}
$this->
fl
ashHelper->addErrorFlash($con
fi
guration, $exception->getFlash());
return $this->redirectHandler->redirectToReferer($con
fi
guration);
}
$postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::DELETE, $con
fi
guration, $resource);
}
if (!$con
fi
guration->isHtmlRequest()) {
return $this->createRestView($con
fi
guration, null, Response::HTTP_NO_CONTENT);
}
$this->
fl
ashHelper->addSuccessFlash($con
fi
guration, ResourceActions::BULK_DELETE);
if (isset($postEvent)) {
$postEventResponse = $postEvent->getResponse();
if (null !== $postEventResponse) {
return $postEventResponse;
}
}
return $this->redirectHandler->redirectToIndex($con
fi
guration);
}
public function applyStateMachineTransitionAction(Request $request): Response
{
$stateMachine = $this->getStateMachine();
$con
fi
guration = $this->requestCon
fi
gurationFactory->create($this->metadata, $request);
$this->isGrantedOr403($con
fi
guration, ResourceActions::UPDATE);
$resource = $this->
fi
ndOr404($con
fi
guration);
if ($con
fi
guration->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, $con
fi
guration, $resource);
if ($event->isStopped() && !$con
fi
guration->isHtmlRequest()) {
throw new HttpException($event->getErrorCode(), $event->getMessage());
}
if ($event->isStopped()) {
$this->
fl
ashHelper->addFlashFromEvent($con
fi
guration, $event);
$eventResponse = $event->getResponse();
if (null !== $eventResponse) {
return $eventResponse;
}
return $this->redirectHandler->redirectToResource($con
fi
guration, $resource);
}
if (!$stateMachine->can($con
fi
guration, $resource)) {
throw new BadRequestHttpException();
}
try {
$this->resourceUpdateHandler->handle($resource, $con
fi
guration, $this->manager);
} catch (UpdateHandlingException $exception) {
if (!$con
fi
guration->isHtmlRequest()) {
return $this->createRestView($con
fi
guration, $resource, $exception->getApiResponseCode());
}
$this->
fl
ashHelper->addErrorFlash($con
fi
guration, $exception->getFlash());
return $this->redirectHandler->redirectToReferer($con
fi
guration);
}
if ($con
fi
guration->isHtmlRequest()) {
$this->
fl
ashHelper->addSuccessFlash($con
fi
guration, ResourceActions::UPDATE, $resource);
}
$postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::UPDATE, $con
fi
guration, $resource);
if (!$con
fi
guration->isHtmlRequest()) {
if ($con
fi
guration->getParameters()->get('return_content', true)) {
return $this->createRestView($con
fi
guration, $resource, Response::HTTP_OK);
}
return $this->createRestView($con
fi
guration, null, Response::HTTP_NO_CONTENT);
}
$postEventResponse = $postEvent->getResponse();
if (null !== $postEventResponse) {
return $postEventResponse;
}
return $this->redirectHandler->redirectToResource($con
fi
guration, $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(RequestCon
fi
guration $con
fi
guration, string $permission): void
{
if (!$con
fi
guration->hasPermission()) {
return;
}
$permission = $con
fi
guration->getPermission($permission);
if (!$this->authorizationChecker->isGranted($con
fi
guration, $permission)) {
throw new AccessDeniedException();
}
}
/**
* @throws NotFoundHttpException
*/
protected function
fi
ndOr404(RequestCon
fi
guration $con
fi
guration): ResourceInterface
{
if (null === $resource = $this->singleResourceProvider->get($con
fi
guration, $this->repository)) {
throw new NotFoundHttpException(sprintf('The "%s" has not been found', $this->metadata->getHumanizedName()));
}
return $resource;
}
/**
* @param mixed $data
*/
protected function createRestView(RequestCon
fi
guration $con
fi
guration, $data, int $statusCode = null): Response
{
if (null === $this->viewHandler) {
throw new LogicException('You can not use the "non-html" request if FriendsOfSymfony Rest Bundle is not available. Try running "composer require friendsofsymfony/rest-bundle".');
}
$view = View::create($data, $statusCode);
return $this->viewHandler->handle($con
fi
guration, $view);
}
protected function getStateMachine(): StateMachineInterface
{
if (null === $this->stateMachine) {
throw new LogicException('You can not use the "state-machine" if Winzou State Machine Bundle is not available. Try running "composer require winzou/state-machine-bundle".');
}
return $this->stateMachine;
}
}
ResourceController
<?php
/*
* This
fi
le is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
*
fi
le that was distributed with this source code.
*/
declare(strict_types=1);
namespace SyliusBundleResourceBundleController;
use DoctrinePersistenceObjectManager;
use FOSRestBundleViewView;
use SyliusBundleResourceBundleEventResourceControllerEvent;
use SyliusComponentResourceExceptionDeleteHandlingException;
use SyliusComponentResourceExceptionUpdateHandlingException;
use SyliusComponentResourceFactoryFactoryInterface;
use SyliusComponentResourceMetadataMetadataInterface;
use SyliusComponentResourceModelResourceInterface;
use SyliusComponentResourceRepositoryRepositoryInterface;
use SyliusComponentResourceResourceActions;
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 RequestCon
fi
gurationFactoryInterface $requestCon
fi
gurationFactory;
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 $
fl
ashHelper;
protected AuthorizationCheckerInterface $authorizationChecker;
protected EventDispatcherInterface $eventDispatcher;
protected ?StateMachineInterface $stateMachine;
protected ResourceUpdateHandlerInterface $resourceUpdateHandler;
protected ResourceDeleteHandlerInterface $resourceDeleteHandler;
public function __construct(
MetadataInterface $metadata,
RequestCon
fi
gurationFactoryInterface $requestCon
fi
gurationFactory,
?ViewHandlerInterface $viewHandler,
RepositoryInterface $repository,
FactoryInterface $factory,
NewResourceFactoryInterface $newResourceFactory,
ObjectManager $manager,
SingleResourceProviderInterface $singleResourceProvider,
ResourcesCollectionProviderInterface $resourcesFinder,
ResourceFormFactoryInterface $resourceFormFactory,
RedirectHandlerInterface $redirectHandler,
FlashHelperInterface $
fl
ashHelper,
AuthorizationCheckerInterface $authorizationChecker,
EventDispatcherInterface $eventDispatcher,
?StateMachineInterface $stateMachine,
ResourceUpdateHandlerInterface $resourceUpdateHandler,
ResourceDeleteHandlerInterface $resourceDeleteHandler,
) {
$this->metadata = $metadata;
$this->requestCon
fi
gurationFactory = $requestCon
fi
gurationFactory;
$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->
fl
ashHelper = $
fl
ashHelper;
$this->authorizationChecker = $authorizationChecker;
$this->eventDispatcher = $eventDispatcher;
$this->stateMachine = $stateMachine;
$this->resourceUpdateHandler = $resourceUpdateHandler;
$this->resourceDeleteHandler = $resourceDeleteHandler;
}
public function showAction(Request $request): Response
{
$con
fi
guration = $this->requestCon
fi
gurationFactory->create($this->metadata, $request);
$this->isGrantedOr403($con
fi
guration, ResourceActions::SHOW);
$resource = $this->
fi
ndOr404($con
fi
guration);
$event = $this->eventDispatcher->dispatch(ResourceActions::SHOW, $con
fi
guration, $resource);
$eventResponse = $event->getResponse();
if (null !== $eventResponse) {
return $eventResponse;
}
if ($con
fi
guration->isHtmlRequest()) {
return $this->render($con
fi
guration->getTemplate(ResourceActions::SHOW . '.html'), [
'con
fi
guration' => $con
fi
guration,
'metadata' => $this->metadata,
'resource' => $resource,
$this->metadata->getName() => $resource,
]);
}
return $this->createRestView($con
fi
guration, $resource);
}
public function indexAction(Request $request): Response
{
$con
fi
guration = $this->requestCon
fi
gurationFactory->create($this->metadata, $request);
$this->isGrantedOr403($con
fi
guration, ResourceActions::INDEX);
$resources = $this->resourcesCollectionProvider->get($con
fi
guration, $this->repository);
$event = $this->eventDispatcher->dispatchMultiple(ResourceActions::INDEX, $con
fi
guration, $resources);
$eventResponse = $event->getResponse();
if (null !== $eventResponse) {
return $eventResponse;
}
if ($con
fi
guration->isHtmlRequest()) {
return $this->render($con
fi
guration->getTemplate(ResourceActions::INDEX . '.html'), [
'con
fi
guration' => $con
fi
guration,
'metadata' => $this->metadata,
'resources' => $resources,
$this->metadata->getPluralName() => $resources,
]);
}
return $this->createRestView($con
fi
guration, $resources);
}
public function createAction(Request $request): Response
{
$con
fi
guration = $this->requestCon
fi
gurationFactory->create($this->metadata, $request);
$this->isGrantedOr403($con
fi
guration, ResourceActions::CREATE);
$newResource = $this->newResourceFactory->create($con
fi
guration, $this->factory);
$form = $this->resourceFormFactory->create($con
fi
guration, $newResource);
$form->handleRequest($request);
if ($request->isMethod('POST') && $form->isSubmitted() && $form->isValid()) {
$newResource = $form->getData();
$event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::CREATE, $con
fi
guration, $newResource);
if ($event->isStopped() && !$con
fi
guration->isHtmlRequest()) {
throw new HttpException($event->getErrorCode(), $event->getMessage());
}
if ($event->isStopped()) {
$this->
fl
ashHelper->addFlashFromEvent($con
fi
guration, $event);
$eventResponse = $event->getResponse();
if (null !== $eventResponse) {
return $eventResponse;
}
return $this->redirectHandler->redirectToIndex($con
fi
guration, $newResource);
}
if ($con
fi
guration->hasStateMachine()) {
$stateMachine = $this->getStateMachine();
$stateMachine->apply($con
fi
guration, $newResource);
}
$this->repository->add($newResource);
if ($con
fi
guration->isHtmlRequest()) {
$this->
fl
ashHelper->addSuccessFlash($con
fi
guration, ResourceActions::CREATE, $newResource);
}
$postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::CREATE, $con
fi
guration, $newResource);
if (!$con
fi
guration->isHtmlRequest()) {
return $this->createRestView($con
fi
guration, $newResource, Response::HTTP_CREATED);
}
$postEventResponse = $postEvent->getResponse();
if (null !== $postEventResponse) {
return $postEventResponse;
}
return $this->redirectHandler->redirectToResource($con
fi
guration, $newResource);
}
if ($request->isMethod('POST') && $form->isSubmitted() && !$form->isValid()) {
$responseCode = Response::HTTP_UNPROCESSABLE_ENTITY;
}
if (!$con
fi
guration->isHtmlRequest()) {
return $this->createRestView($con
fi
guration, $form, Response::HTTP_BAD_REQUEST);
}
$initializeEvent = $this->eventDispatcher->dispatchInitializeEvent(ResourceActions::CREATE, $con
fi
guration, $newResource);
$initializeEventResponse = $initializeEvent->getResponse();
if (null !== $initializeEventResponse) {
return $initializeEventResponse;
}
return $this->render($con
fi
guration->getTemplate(ResourceActions::CREATE . '.html'), [
'con
fi
guration' => $con
fi
guration,
'metadata' => $this->metadata,
'resource' => $newResource,
$this->metadata->getName() => $newResource,
'form' => $form->createView(),
], null, $responseCode ?? Response::HTTP_OK);
}
public function updateAction(Request $request): Response
{
$con
fi
guration = $this->requestCon
fi
gurationFactory->create($this->metadata, $request);
$this->isGrantedOr403($con
fi
guration, ResourceActions::UPDATE);
$resource = $this->
fi
ndOr404($con
fi
guration);
$form = $this->resourceFormFactory->create($con
fi
guration, $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, $con
fi
guration, $resource);
if ($event->isStopped() && !$con
fi
guration->isHtmlRequest()) {
throw new HttpException($event->getErrorCode(), $event->getMessage());
}
if ($event->isStopped()) {
$this->
fl
ashHelper->addFlashFromEvent($con
fi
guration, $event);
$eventResponse = $event->getResponse();
if (null !== $eventResponse) {
return $eventResponse;
}
return $this->redirectHandler->redirectToResource($con
fi
guration, $resource);
}
try {
$this->resourceUpdateHandler->handle($resource, $con
fi
guration, $this->manager);
} catch (UpdateHandlingException $exception) {
if (!$con
fi
guration->isHtmlRequest()) {
return $this->createRestView($con
fi
guration, $form, $exception->getApiResponseCode());
}
$this->
fl
ashHelper->addErrorFlash($con
fi
guration, $exception->getFlash());
return $this->redirectHandler->redirectToReferer($con
fi
guration);
}
if ($con
fi
guration->isHtmlRequest()) {
$this->
fl
ashHelper->addSuccessFlash($con
fi
guration, ResourceActions::UPDATE, $resource);
}
$postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::UPDATE, $con
fi
guration, $resource);
if (!$con
fi
guration->isHtmlRequest()) {
if ($con
fi
guration->getParameters()->get('return_content', false)) {
return $this->createRestView($con
fi
guration, $resource, Response::HTTP_OK);
}
return $this->createRestView($con
fi
guration, null, Response::HTTP_NO_CONTENT);
}
$postEventResponse = $postEvent->getResponse();
if (null !== $postEventResponse) {
return $postEventResponse;
}
return $this->redirectHandler->redirectToResource($con
fi
guration, $resource);
}
if (in_array($request->getMethod(), ['POST', 'PUT', 'PATCH'], true) && $form->isSubmitted() && !$form->isValid()) {
$responseCode = Response::HTTP_UNPROCESSABLE_ENTITY;
}
if (!$con
fi
guration->isHtmlRequest()) {
return $this->createRestView($con
fi
guration, $form, Response::HTTP_BAD_REQUEST);
}
$initializeEvent = $this->eventDispatcher->dispatchInitializeEvent(ResourceActions::UPDATE, $con
fi
guration, $resource);
$initializeEventResponse = $initializeEvent->getResponse();
if (null !== $initializeEventResponse) {
return $initializeEventResponse;
}
return $this->render($con
fi
guration->getTemplate(ResourceActions::UPDATE . '.html'), [
'con
fi
guration' => $con
fi
guration,
'metadata' => $this->metadata,
'resource' => $resource,
$this->metadata->getName() => $resource,
'form' => $form->createView(),
], null, $responseCode ?? Response::HTTP_OK);
}
public function deleteAction(Request $request): Response
{
$con
fi
guration = $this->requestCon
fi
gurationFactory->create($this->metadata, $request);
$this->isGrantedOr403($con
fi
guration, ResourceActions::DELETE);
$resource = $this->
fi
ndOr404($con
fi
guration);
if ($con
fi
guration->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, $con
fi
guration, $resource);
if ($event->isStopped() && !$con
fi
guration->isHtmlRequest()) {
throw new HttpException($event->getErrorCode(), $event->getMessage());
}
if ($event->isStopped()) {
$this->
fl
ashHelper->addFlashFromEvent($con
fi
guration, $event);
$eventResponse = $event->getResponse();
if (null !== $eventResponse) {
return $eventResponse;
}
return $this->redirectHandler->redirectToIndex($con
fi
guration, $resource);
}
try {
$this->resourceDeleteHandler->handle($resource, $this->repository);
} catch (DeleteHandlingException $exception) {
if (!$con
fi
guration->isHtmlRequest()) {
return $this->createRestView($con
fi
guration, null, $exception->getApiResponseCode());
}
$this->
fl
ashHelper->addErrorFlash($con
fi
guration, $exception->getFlash());
return $this->redirectHandler->redirectToReferer($con
fi
guration);
}
if ($con
fi
guration->isHtmlRequest()) {
$this->
fl
ashHelper->addSuccessFlash($con
fi
guration, ResourceActions::DELETE, $resource);
}
$postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::DELETE, $con
fi
guration, $resource);
if (!$con
fi
guration->isHtmlRequest()) {
return $this->createRestView($con
fi
guration, null, Response::HTTP_NO_CONTENT);
}
$postEventResponse = $postEvent->getResponse();
if (null !== $postEventResponse) {
return $postEventResponse;
}
return $this->redirectHandler->redirectToIndex($con
fi
guration, $resource);
}
public function bulkDeleteAction(Request $request): Response
{
$con
fi
guration = $this->requestCon
fi
gurationFactory->create($this->metadata, $request);
$this->isGrantedOr403($con
fi
guration, ResourceActions::BULK_DELETE);
$resources = $this->resourcesCollectionProvider->get($con
fi
guration, $this->repository);
if (
$con
fi
guration->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, $con
fi
guration, $resources);
foreach ($resources as $resource) {
$event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::DELETE, $con
fi
guration, $resource);
if ($event->isStopped() && !$con
fi
guration->isHtmlRequest()) {
throw new HttpException($event->getErrorCode(), $event->getMessage());
}
if ($event->isStopped()) {
$this->
fl
ashHelper->addFlashFromEvent($con
fi
guration, $event);
$eventResponse = $event->getResponse();
if (null !== $eventResponse) {
return $eventResponse;
}
return $this->redirectHandler->redirectToIndex($con
fi
guration, $resource);
}
try {
$this->resourceDeleteHandler->handle($resource, $this->repository);
} catch (DeleteHandlingException $exception) {
if (!$con
fi
guration->isHtmlRequest()) {
return $this->createRestView($con
fi
guration, null, $exception->getApiResponseCode());
}
$this->
fl
ashHelper->addErrorFlash($con
fi
guration, $exception->getFlash());
return $this->redirectHandler->redirectToReferer($con
fi
guration);
}
$postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::DELETE, $con
fi
guration, $resource);
}
if (!$con
fi
guration->isHtmlRequest()) {
return $this->createRestView($con
fi
guration, null, Response::HTTP_NO_CONTENT);
}
$this->
fl
ashHelper->addSuccessFlash($con
fi
guration, ResourceActions::BULK_DELETE);
if (isset($postEvent)) {
$postEventResponse = $postEvent->getResponse();
if (null !== $postEventResponse) {
return $postEventResponse;
}
}
return $this->redirectHandler->redirectToIndex($con
fi
guration);
}
public function applyStateMachineTransitionAction(Request $request): Response
{
$stateMachine = $this->getStateMachine();
$con
fi
guration = $this->requestCon
fi
gurationFactory->create($this->metadata, $request);
$this->isGrantedOr403($con
fi
guration, ResourceActions::UPDATE);
$resource = $this->
fi
ndOr404($con
fi
guration);
if ($con
fi
guration->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, $con
fi
guration, $resource);
if ($event->isStopped() && !$con
fi
guration->isHtmlRequest()) {
throw new HttpException($event->getErrorCode(), $event->getMessage());
}
if ($event->isStopped()) {
$this->
fl
ashHelper->addFlashFromEvent($con
fi
guration, $event);
$eventResponse = $event->getResponse();
if (null !== $eventResponse) {
return $eventResponse;
}
return $this->redirectHandler->redirectToResource($con
fi
guration, $resource);
}
if (!$stateMachine->can($con
fi
guration, $resource)) {
throw new BadRequestHttpException();
}
try {
$this->resourceUpdateHandler->handle($resource, $con
fi
guration, $this->manager);
} catch (UpdateHandlingException $exception) {
if (!$con
fi
guration->isHtmlRequest()) {
return $this->createRestView($con
fi
guration, $resource, $exception->getApiResponseCode());
}
$this->
fl
ashHelper->addErrorFlash($con
fi
guration, $exception->getFlash());
return $this->redirectHandler->redirectToReferer($con
fi
guration);
}
if ($con
fi
guration->isHtmlRequest()) {
$this->
fl
ashHelper->addSuccessFlash($con
fi
guration, ResourceActions::UPDATE, $resource);
}
$postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::UPDATE, $con
fi
guration, $resource);
if (!$con
fi
guration->isHtmlRequest()) {
if ($con
fi
guration->getParameters()->get('return_content', true)) {
return $this->createRestView($con
fi
guration, $resource, Response::HTTP_OK);
}
return $this->createRestView($con
fi
guration, null, Response::HTTP_NO_CONTENT);
}
$postEventResponse = $postEvent->getResponse();
if (null !== $postEventResponse) {
return $postEventResponse;
}
return $this->redirectHandler->redirectToResource($con
fi
guration, $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(RequestCon
fi
guration $con
fi
guration, string $permission): void
{
if (!$con
fi
guration->hasPermission()) {
return;
}
$permission = $con
fi
guration->getPermission($permission);
if (!$this->authorizationChecker->isGranted($con
fi
guration, $permission)) {
throw new AccessDeniedException();
}
}
/**
* @throws NotFoundHttpException
*/
protected function
fi
ndOr404(RequestCon
fi
guration $con
fi
guration): ResourceInterface
{
if (null === $resource = $this->singleResourceProvider->get($con
fi
guration, $this->repository)) {
throw new NotFoundHttpException(sprintf('The "%s" has not been found', $this->metadata->getHumanizedName()));
}
return $resource;
}
/**
* @param mixed $data
*/
protected function createRestView(RequestCon
fi
guration $con
fi
guration, $data, int $statusCode = null): Response
{
if (null === $this->viewHandler) {
throw new LogicException('You can not use the "non-html" request if FriendsOfSymfony Rest Bundle is not available. Try running "composer require friendsofsymfony/rest-bundle".');
}
$view = View::create($data, $statusCode);
return $this->viewHandler->handle($con
fi
guration, $view);
}
protected function getStateMachine(): StateMachineInterface
{
if (null === $this->stateMachine) {
throw new LogicException('You can not use the "state-machine" if Winzou State Machine Bundle is not available. Try running "composer require winzou/state-machine-bundle".');
}
return $this->stateMachine;
}
}
ResourceController
!
interface ProviderInterface
{
public function provide(Operation $operation, Context $context): object|iterable|null;
}
interface ProcessorInterface
{
public function process(mixed $data, Operation $operation, Context $context): mixed;
}
#[AsResource]
#[Index(grid: BookGrid::class)]
#[Create]
class Book implements ResourceInterface
{
}
$grid->addGrid(GridBuilder::create('app_user', '%app.model.user.class%')
->setLimits([10, 25, 50, 100])
->addField(
Field::create('name', 'twig')
->setLabel('Name')
->setSortable(true)
)
->addFilter(
Filter::create('name', 'string')
->setLabel('app.ui.name')
->setEnabled(true)
->setFormOptions(['type' => 'contains'])
)
->addActionGroup(MainActionGroup::create(
Action::create('create', 'create')
))
;
Is Sylius DX still great?…
Is Sylius DX still great?…
Of course
Q&A
We are hiring!
Łukasz Chruściel
Mateusz Zalewski
@lchrusciel
@mpzalewski
Commerce Weavers @commerceweavers
Thank you!

More Related Content

Similar to Pitfalls of Sylius development

The Naked Bundle - Symfony Usergroup Belgium
The Naked Bundle - Symfony Usergroup BelgiumThe Naked Bundle - Symfony Usergroup Belgium
The Naked Bundle - Symfony Usergroup BelgiumMatthias Noback
 
The Naked Bundle - Symfony Live London 2014
The Naked Bundle - Symfony Live London 2014The Naked Bundle - Symfony Live London 2014
The Naked Bundle - Symfony Live London 2014Matthias Noback
 
Creating a modern web application using Symfony API Platform, ReactJS and Red...
Creating a modern web application using Symfony API Platform, ReactJS and Red...Creating a modern web application using Symfony API Platform, ReactJS and Red...
Creating a modern web application using Symfony API Platform, ReactJS and Red...Jesus Manuel Olivas
 
Androidreadme
AndroidreadmeAndroidreadme
Androidreadmeiderdik
 
The Naked Bundle - Tryout
The Naked Bundle - TryoutThe Naked Bundle - Tryout
The Naked Bundle - TryoutMatthias Noback
 
Bring the fun back to java
Bring the fun back to javaBring the fun back to java
Bring the fun back to javaciklum_ods
 
Creating a modern web application using Symfony API Platform Atlanta
Creating a modern web application using  Symfony API Platform AtlantaCreating a modern web application using  Symfony API Platform Atlanta
Creating a modern web application using Symfony API Platform AtlantaJesus Manuel Olivas
 
Spring training
Spring trainingSpring training
Spring trainingTechFerry
 
The Naked Bundle - Symfony Barcelona
The Naked Bundle - Symfony BarcelonaThe Naked Bundle - Symfony Barcelona
The Naked Bundle - Symfony BarcelonaMatthias Noback
 
SymfonyCon Berlin 2016 - Symfony Plugin for PhpStorm - 3 years later
SymfonyCon Berlin 2016 - Symfony Plugin for PhpStorm - 3 years laterSymfonyCon Berlin 2016 - Symfony Plugin for PhpStorm - 3 years later
SymfonyCon Berlin 2016 - Symfony Plugin for PhpStorm - 3 years laterHaehnchen
 
Drupal 8, Where Did the Code Go? From Info Hook to Plugin
Drupal 8, Where Did the Code Go? From Info Hook to PluginDrupal 8, Where Did the Code Go? From Info Hook to Plugin
Drupal 8, Where Did the Code Go? From Info Hook to PluginAcquia
 
api-platform: the ultimate API platform
api-platform: the ultimate API platformapi-platform: the ultimate API platform
api-platform: the ultimate API platformStefan Adolf
 
Entities in drupal 7
Entities in drupal 7Entities in drupal 7
Entities in drupal 7Zsolt Tasnadi
 
Using API Platform to build ticketing system #symfonycon
Using API Platform to build ticketing system #symfonyconUsing API Platform to build ticketing system #symfonycon
Using API Platform to build ticketing system #symfonyconAntonio Peric-Mazar
 
Deploy Serverless Apps with Python: AWS Chalice Deep Dive (DEV427-R2) - AWS r...
Deploy Serverless Apps with Python: AWS Chalice Deep Dive (DEV427-R2) - AWS r...Deploy Serverless Apps with Python: AWS Chalice Deep Dive (DEV427-R2) - AWS r...
Deploy Serverless Apps with Python: AWS Chalice Deep Dive (DEV427-R2) - AWS r...Amazon Web Services
 
JMP103 : Extending Your App Arsenal With OpenSocial
JMP103 : Extending Your App Arsenal With OpenSocialJMP103 : Extending Your App Arsenal With OpenSocial
JMP103 : Extending Your App Arsenal With OpenSocialRyan Baxter
 
IBM Connect 2014 - JMP103: Extending Your Application Arsenal With OpenSocial
IBM Connect 2014 - JMP103: Extending Your Application Arsenal With OpenSocialIBM Connect 2014 - JMP103: Extending Your Application Arsenal With OpenSocial
IBM Connect 2014 - JMP103: Extending Your Application Arsenal With OpenSocialIBM Connections Developers
 
Rails Plugins - Linux For You, March 2011 Issue
Rails Plugins - Linux For You, March 2011 IssueRails Plugins - Linux For You, March 2011 Issue
Rails Plugins - Linux For You, March 2011 IssueSagar Arlekar
 

Similar to Pitfalls of Sylius development (20)

The Naked Bundle - Symfony Usergroup Belgium
The Naked Bundle - Symfony Usergroup BelgiumThe Naked Bundle - Symfony Usergroup Belgium
The Naked Bundle - Symfony Usergroup Belgium
 
The Naked Bundle - Symfony Live London 2014
The Naked Bundle - Symfony Live London 2014The Naked Bundle - Symfony Live London 2014
The Naked Bundle - Symfony Live London 2014
 
Creating a modern web application using Symfony API Platform, ReactJS and Red...
Creating a modern web application using Symfony API Platform, ReactJS and Red...Creating a modern web application using Symfony API Platform, ReactJS and Red...
Creating a modern web application using Symfony API Platform, ReactJS and Red...
 
Androidreadme
AndroidreadmeAndroidreadme
Androidreadme
 
The Naked Bundle - Tryout
The Naked Bundle - TryoutThe Naked Bundle - Tryout
The Naked Bundle - Tryout
 
Bring the fun back to java
Bring the fun back to javaBring the fun back to java
Bring the fun back to java
 
Creating a modern web application using Symfony API Platform Atlanta
Creating a modern web application using  Symfony API Platform AtlantaCreating a modern web application using  Symfony API Platform Atlanta
Creating a modern web application using Symfony API Platform Atlanta
 
Complex Sites with Silex
Complex Sites with SilexComplex Sites with Silex
Complex Sites with Silex
 
Spring training
Spring trainingSpring training
Spring training
 
The Naked Bundle - Symfony Barcelona
The Naked Bundle - Symfony BarcelonaThe Naked Bundle - Symfony Barcelona
The Naked Bundle - Symfony Barcelona
 
SymfonyCon Berlin 2016 - Symfony Plugin for PhpStorm - 3 years later
SymfonyCon Berlin 2016 - Symfony Plugin for PhpStorm - 3 years laterSymfonyCon Berlin 2016 - Symfony Plugin for PhpStorm - 3 years later
SymfonyCon Berlin 2016 - Symfony Plugin for PhpStorm - 3 years later
 
Drupal 8, Where Did the Code Go? From Info Hook to Plugin
Drupal 8, Where Did the Code Go? From Info Hook to PluginDrupal 8, Where Did the Code Go? From Info Hook to Plugin
Drupal 8, Where Did the Code Go? From Info Hook to Plugin
 
api-platform: the ultimate API platform
api-platform: the ultimate API platformapi-platform: the ultimate API platform
api-platform: the ultimate API platform
 
Entities in drupal 7
Entities in drupal 7Entities in drupal 7
Entities in drupal 7
 
Using API Platform to build ticketing system #symfonycon
Using API Platform to build ticketing system #symfonyconUsing API Platform to build ticketing system #symfonycon
Using API Platform to build ticketing system #symfonycon
 
Grails Advanced
Grails Advanced Grails Advanced
Grails Advanced
 
Deploy Serverless Apps with Python: AWS Chalice Deep Dive (DEV427-R2) - AWS r...
Deploy Serverless Apps with Python: AWS Chalice Deep Dive (DEV427-R2) - AWS r...Deploy Serverless Apps with Python: AWS Chalice Deep Dive (DEV427-R2) - AWS r...
Deploy Serverless Apps with Python: AWS Chalice Deep Dive (DEV427-R2) - AWS r...
 
JMP103 : Extending Your App Arsenal With OpenSocial
JMP103 : Extending Your App Arsenal With OpenSocialJMP103 : Extending Your App Arsenal With OpenSocial
JMP103 : Extending Your App Arsenal With OpenSocial
 
IBM Connect 2014 - JMP103: Extending Your Application Arsenal With OpenSocial
IBM Connect 2014 - JMP103: Extending Your Application Arsenal With OpenSocialIBM Connect 2014 - JMP103: Extending Your Application Arsenal With OpenSocial
IBM Connect 2014 - JMP103: Extending Your Application Arsenal With OpenSocial
 
Rails Plugins - Linux For You, March 2011 Issue
Rails Plugins - Linux For You, March 2011 IssueRails Plugins - Linux For You, March 2011 Issue
Rails Plugins - Linux For You, March 2011 Issue
 

More from Łukasz Chruściel

Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesŁukasz Chruściel
 
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Łukasz Chruściel
 
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Łukasz Chruściel
 
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Łukasz Chruściel
 
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...Łukasz Chruściel
 
4Developers - Rozterki i decyzje.pdf
4Developers - Rozterki i decyzje.pdf4Developers - Rozterki i decyzje.pdf
4Developers - Rozterki i decyzje.pdfŁukasz Chruściel
 
4Developers - Sylius CRUD generation revisited.pdf
4Developers - Sylius CRUD generation revisited.pdf4Developers - Sylius CRUD generation revisited.pdf
4Developers - Sylius CRUD generation revisited.pdfŁukasz Chruściel
 
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Łukasz Chruściel
 
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Łukasz Chruściel
 
How to optimize background processes.pdf
How to optimize background processes.pdfHow to optimize background processes.pdf
How to optimize background processes.pdfŁukasz Chruściel
 
SymfonyCon - Dilemmas and decisions..pdf
SymfonyCon - Dilemmas and decisions..pdfSymfonyCon - Dilemmas and decisions..pdf
SymfonyCon - Dilemmas and decisions..pdfŁukasz Chruściel
 
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Łukasz Chruściel
 
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Łukasz Chruściel
 
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Łukasz Chruściel
 
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Łukasz Chruściel
 
A short tale about state machine
A short tale about state machineA short tale about state machine
A short tale about state machineŁukasz Chruściel
 
A short tale about state machine
A short tale about state machineA short tale about state machine
A short tale about state machineŁukasz Chruściel
 
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Łukasz Chruściel
 
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Łukasz Chruściel
 

More from Łukasz Chruściel (20)

Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New Features
 
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
 
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
 
Why do I love and hate php?
Why do I love and hate php?Why do I love and hate php?
Why do I love and hate php?
 

Recently uploaded

Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideChristina Lin
 
Salesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantSalesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantAxelRicardoTrocheRiq
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providermohitmore19
 
chapter--4-software-project-planning.ppt
chapter--4-software-project-planning.pptchapter--4-software-project-planning.ppt
chapter--4-software-project-planning.pptkotipi9215
 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEOrtus Solutions, Corp
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsAlberto González Trastoy
 
What is Binary Language? Computer Number Systems
What is Binary Language?  Computer Number SystemsWhat is Binary Language?  Computer Number Systems
What is Binary Language? Computer Number SystemsJheuzeDellosa
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...OnePlan Solutions
 
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataAdobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataBradBedford3
 
Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)OPEN KNOWLEDGE GmbH
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...ICS
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...kellynguyen01
 
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...gurkirankumar98700
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...stazi3110
 
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...soniya singh
 
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio, Inc.
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityNeo4j
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfkalichargn70th171
 

Recently uploaded (20)

Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
 
Salesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantSalesforce Certified Field Service Consultant
Salesforce Certified Field Service Consultant
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service provider
 
Exploring iOS App Development: Simplifying the Process
Exploring iOS App Development: Simplifying the ProcessExploring iOS App Development: Simplifying the Process
Exploring iOS App Development: Simplifying the Process
 
chapter--4-software-project-planning.ppt
chapter--4-software-project-planning.pptchapter--4-software-project-planning.ppt
chapter--4-software-project-planning.ppt
 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
 
What is Binary Language? Computer Number Systems
What is Binary Language?  Computer Number SystemsWhat is Binary Language?  Computer Number Systems
What is Binary Language? Computer Number Systems
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...
 
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataAdobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
 
Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
 
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
 
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
 
Call Girls In Mukherjee Nagar 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...Call Girls In Mukherjee Nagar 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
 
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered Sustainability
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
 

Pitfalls of Sylius development

  • 3. Why do we do this?
  • 4.
  • 5.
  • 6.
  • 7.
  • 9. AI is not a threat for now
  • 10.
  • 11. Q&A
  • 13. N+1
  • 18.
  • 19. O(1)
  • 21. How many queries are executed here? { "@context": "/api/contexts/Order", "@id": "/api/orders", "@type": "hydra:Collection", "hydra:totalItems": 3, "hydra:member": [ { "@id": "/api/orders/1", "@type": "Order", "id": 1, "orderItems": [ "/api/order_items/1", "/api/order_items/2" ] }, { “…”: “…“ } ] } Amount of orders: 3 Every order associated with 2 items Sample query on the left No total fi eld
  • 22.
  • 23.
  • 24. O(M)
  • 25. Order list with additional fi eld
  • 26. How many queries are executed here? { "@context": "/api/contexts/Order", "@id": "/api/orders", "@type": "hydra:Collection", "hydra:totalItems": 3, "hydra:member": [ { "@id": "/api/orders/1", "@type": "Order", "id": 1, "orderItems": [ "/api/order_items/1", "/api/order_items/2" ], "total": 7000 }, { “…”: “…“ } ] } Amount of orders: 3 Every order associated with 2 items Sample query on the left Added “total()” as a function of product price and quantity of item
  • 27.
  • 29.
  • 32. #[ORMOneToMany(fetch: ‘LAZY')] => 11 #[ORMOneToMany(fetch: ‘EXTRA_LAZY')] => 11 #[ORMOneToMany(fetch: ‘EAGER')] => 5 (OrderItem and Product) #[ORMOneToMany(fetch: ‘EAGER')] (OrderItem and Product) + Serialisation groups => 4
  • 34. final class LoadItemsAndProductsExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface { private function apply(QueryBuilder $queryBuilder): void { $queryBuilder ->addSelect('oi', 'p') ->join(OrderItem::class, 'oi', Join::WITH, 'oi.originOrder = o') ->join(Product::class, 'p', Join::WITH, 'oi.product = p') ; } }
  • 35.
  • 37. { "@context": "/api/contexts/Order", "@id": "/api/orders", "@type": "hydra:Collection", "hydra:totalItems": 3, "hydra:member": [ { "@id": "/api/orders/1", "@type": "Order", "id": 1, "orderItems": [ "/api/order_items/1", "/api/order_items/2" ], "total": 7000 }, { "@id": "/api/order_items/1", "@type": "OrderItem", "id": 1, "product": "/api/products/1", "quantity": 2, "originOrder": "/api/orders/1", "price": 2000 }, { "@id": "/api/products/1", "@type": "Product", "id": 1, "name": "T-Shirt", "price": 1000 }, { "@id": "/api/order_items/2", "@type": "OrderItem", "id": 2, "product": "/api/products/2", "quantity": 1, "originOrder": "/api/orders/1", "price": 5000 }, { “…”: “…” } ] }
  • 38. So is single query an ultimate solution?
  • 41. Does it need to be sequential?
  • 42. Or defer it 😉
  • 43. Locking mechanism of product variants (inventory)
  • 45. Concurrency from the same user
  • 47. How does Doctrine collection handling works?
  • 48. Tests
  • 49.
  • 50.
  • 51.
  • 53. You don’t need to do BDD to work with Sylius Although we ecourage you to do so
  • 54. No. of Behat scenarios in our latest successfull Sylius project:
  • 55. 0 No. of Behat scenarios in our latest successfull Sylius project:
  • 63. What we want to achieve
  • 65. Core Team is actively working on it
  • 68.
  • 69. <?php /* * This fi le is part of the Sylius package. * * (c) Sylius Sp. z o.o. * * For the full copyright and license information, please view the LICENSE * fi le that was distributed with this source code. */ declare(strict_types=1); namespace SyliusBundleResourceBundleController; use DoctrinePersistenceObjectManager; use FOSRestBundleViewView; use SyliusBundleResourceBundleEventResourceControllerEvent; use SyliusComponentResourceExceptionDeleteHandlingException; use SyliusComponentResourceExceptionUpdateHandlingException; use SyliusComponentResourceFactoryFactoryInterface; use SyliusComponentResourceMetadataMetadataInterface; use SyliusComponentResourceModelResourceInterface; use SyliusComponentResourceRepositoryRepositoryInterface; use SyliusComponentResourceResourceActions; 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 RequestCon fi gurationFactoryInterface $requestCon fi gurationFactory; 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 $ fl ashHelper; protected AuthorizationCheckerInterface $authorizationChecker; protected EventDispatcherInterface $eventDispatcher; protected ?StateMachineInterface $stateMachine; protected ResourceUpdateHandlerInterface $resourceUpdateHandler; protected ResourceDeleteHandlerInterface $resourceDeleteHandler; public function __construct( MetadataInterface $metadata, RequestCon fi gurationFactoryInterface $requestCon fi gurationFactory, ?ViewHandlerInterface $viewHandler, RepositoryInterface $repository, FactoryInterface $factory, NewResourceFactoryInterface $newResourceFactory, ObjectManager $manager, SingleResourceProviderInterface $singleResourceProvider, ResourcesCollectionProviderInterface $resourcesFinder, ResourceFormFactoryInterface $resourceFormFactory, RedirectHandlerInterface $redirectHandler, FlashHelperInterface $ fl ashHelper, AuthorizationCheckerInterface $authorizationChecker, EventDispatcherInterface $eventDispatcher, ?StateMachineInterface $stateMachine, ResourceUpdateHandlerInterface $resourceUpdateHandler, ResourceDeleteHandlerInterface $resourceDeleteHandler, ) { $this->metadata = $metadata; $this->requestCon fi gurationFactory = $requestCon fi gurationFactory; $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-> fl ashHelper = $ fl ashHelper; $this->authorizationChecker = $authorizationChecker; $this->eventDispatcher = $eventDispatcher; $this->stateMachine = $stateMachine; $this->resourceUpdateHandler = $resourceUpdateHandler; $this->resourceDeleteHandler = $resourceDeleteHandler; } public function showAction(Request $request): Response { $con fi guration = $this->requestCon fi gurationFactory->create($this->metadata, $request); $this->isGrantedOr403($con fi guration, ResourceActions::SHOW); $resource = $this-> fi ndOr404($con fi guration); $event = $this->eventDispatcher->dispatch(ResourceActions::SHOW, $con fi guration, $resource); $eventResponse = $event->getResponse(); if (null !== $eventResponse) { return $eventResponse; } if ($con fi guration->isHtmlRequest()) { return $this->render($con fi guration->getTemplate(ResourceActions::SHOW . '.html'), [ 'con fi guration' => $con fi guration, 'metadata' => $this->metadata, 'resource' => $resource, $this->metadata->getName() => $resource, ]); } return $this->createRestView($con fi guration, $resource); } public function indexAction(Request $request): Response { $con fi guration = $this->requestCon fi gurationFactory->create($this->metadata, $request); $this->isGrantedOr403($con fi guration, ResourceActions::INDEX); $resources = $this->resourcesCollectionProvider->get($con fi guration, $this->repository); $event = $this->eventDispatcher->dispatchMultiple(ResourceActions::INDEX, $con fi guration, $resources); $eventResponse = $event->getResponse(); if (null !== $eventResponse) { return $eventResponse; } if ($con fi guration->isHtmlRequest()) { return $this->render($con fi guration->getTemplate(ResourceActions::INDEX . '.html'), [ 'con fi guration' => $con fi guration, 'metadata' => $this->metadata, 'resources' => $resources, $this->metadata->getPluralName() => $resources, ]); } return $this->createRestView($con fi guration, $resources); } public function createAction(Request $request): Response { $con fi guration = $this->requestCon fi gurationFactory->create($this->metadata, $request); $this->isGrantedOr403($con fi guration, ResourceActions::CREATE); $newResource = $this->newResourceFactory->create($con fi guration, $this->factory); $form = $this->resourceFormFactory->create($con fi guration, $newResource); $form->handleRequest($request); if ($request->isMethod('POST') && $form->isSubmitted() && $form->isValid()) { $newResource = $form->getData(); $event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::CREATE, $con fi guration, $newResource); if ($event->isStopped() && !$con fi guration->isHtmlRequest()) { throw new HttpException($event->getErrorCode(), $event->getMessage()); } if ($event->isStopped()) { $this-> fl ashHelper->addFlashFromEvent($con fi guration, $event); $eventResponse = $event->getResponse(); if (null !== $eventResponse) { return $eventResponse; } return $this->redirectHandler->redirectToIndex($con fi guration, $newResource); } if ($con fi guration->hasStateMachine()) { $stateMachine = $this->getStateMachine(); $stateMachine->apply($con fi guration, $newResource); } $this->repository->add($newResource); if ($con fi guration->isHtmlRequest()) { $this-> fl ashHelper->addSuccessFlash($con fi guration, ResourceActions::CREATE, $newResource); } $postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::CREATE, $con fi guration, $newResource); if (!$con fi guration->isHtmlRequest()) { return $this->createRestView($con fi guration, $newResource, Response::HTTP_CREATED); } $postEventResponse = $postEvent->getResponse(); if (null !== $postEventResponse) { return $postEventResponse; } return $this->redirectHandler->redirectToResource($con fi guration, $newResource); } if ($request->isMethod('POST') && $form->isSubmitted() && !$form->isValid()) { $responseCode = Response::HTTP_UNPROCESSABLE_ENTITY; } if (!$con fi guration->isHtmlRequest()) { return $this->createRestView($con fi guration, $form, Response::HTTP_BAD_REQUEST); } $initializeEvent = $this->eventDispatcher->dispatchInitializeEvent(ResourceActions::CREATE, $con fi guration, $newResource); $initializeEventResponse = $initializeEvent->getResponse(); if (null !== $initializeEventResponse) { return $initializeEventResponse; } return $this->render($con fi guration->getTemplate(ResourceActions::CREATE . '.html'), [ 'con fi guration' => $con fi guration, 'metadata' => $this->metadata, 'resource' => $newResource, $this->metadata->getName() => $newResource, 'form' => $form->createView(), ], null, $responseCode ?? Response::HTTP_OK); } public function updateAction(Request $request): Response { $con fi guration = $this->requestCon fi gurationFactory->create($this->metadata, $request); $this->isGrantedOr403($con fi guration, ResourceActions::UPDATE); $resource = $this-> fi ndOr404($con fi guration); $form = $this->resourceFormFactory->create($con fi guration, $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, $con fi guration, $resource); if ($event->isStopped() && !$con fi guration->isHtmlRequest()) { throw new HttpException($event->getErrorCode(), $event->getMessage()); } if ($event->isStopped()) { $this-> fl ashHelper->addFlashFromEvent($con fi guration, $event); $eventResponse = $event->getResponse(); if (null !== $eventResponse) { return $eventResponse; } return $this->redirectHandler->redirectToResource($con fi guration, $resource); } try { $this->resourceUpdateHandler->handle($resource, $con fi guration, $this->manager); } catch (UpdateHandlingException $exception) { if (!$con fi guration->isHtmlRequest()) { return $this->createRestView($con fi guration, $form, $exception->getApiResponseCode()); } $this-> fl ashHelper->addErrorFlash($con fi guration, $exception->getFlash()); return $this->redirectHandler->redirectToReferer($con fi guration); } if ($con fi guration->isHtmlRequest()) { $this-> fl ashHelper->addSuccessFlash($con fi guration, ResourceActions::UPDATE, $resource); } $postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::UPDATE, $con fi guration, $resource); if (!$con fi guration->isHtmlRequest()) { if ($con fi guration->getParameters()->get('return_content', false)) { return $this->createRestView($con fi guration, $resource, Response::HTTP_OK); } return $this->createRestView($con fi guration, null, Response::HTTP_NO_CONTENT); } $postEventResponse = $postEvent->getResponse(); if (null !== $postEventResponse) { return $postEventResponse; } return $this->redirectHandler->redirectToResource($con fi guration, $resource); } if (in_array($request->getMethod(), ['POST', 'PUT', 'PATCH'], true) && $form->isSubmitted() && !$form->isValid()) { $responseCode = Response::HTTP_UNPROCESSABLE_ENTITY; } if (!$con fi guration->isHtmlRequest()) { return $this->createRestView($con fi guration, $form, Response::HTTP_BAD_REQUEST); } $initializeEvent = $this->eventDispatcher->dispatchInitializeEvent(ResourceActions::UPDATE, $con fi guration, $resource); $initializeEventResponse = $initializeEvent->getResponse(); if (null !== $initializeEventResponse) { return $initializeEventResponse; } return $this->render($con fi guration->getTemplate(ResourceActions::UPDATE . '.html'), [ 'con fi guration' => $con fi guration, 'metadata' => $this->metadata, 'resource' => $resource, $this->metadata->getName() => $resource, 'form' => $form->createView(), ], null, $responseCode ?? Response::HTTP_OK); } public function deleteAction(Request $request): Response { $con fi guration = $this->requestCon fi gurationFactory->create($this->metadata, $request); $this->isGrantedOr403($con fi guration, ResourceActions::DELETE); $resource = $this-> fi ndOr404($con fi guration); if ($con fi guration->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, $con fi guration, $resource); if ($event->isStopped() && !$con fi guration->isHtmlRequest()) { throw new HttpException($event->getErrorCode(), $event->getMessage()); } if ($event->isStopped()) { $this-> fl ashHelper->addFlashFromEvent($con fi guration, $event); $eventResponse = $event->getResponse(); if (null !== $eventResponse) { return $eventResponse; } return $this->redirectHandler->redirectToIndex($con fi guration, $resource); } try { $this->resourceDeleteHandler->handle($resource, $this->repository); } catch (DeleteHandlingException $exception) { if (!$con fi guration->isHtmlRequest()) { return $this->createRestView($con fi guration, null, $exception->getApiResponseCode()); } $this-> fl ashHelper->addErrorFlash($con fi guration, $exception->getFlash()); return $this->redirectHandler->redirectToReferer($con fi guration); } if ($con fi guration->isHtmlRequest()) { $this-> fl ashHelper->addSuccessFlash($con fi guration, ResourceActions::DELETE, $resource); } $postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::DELETE, $con fi guration, $resource); if (!$con fi guration->isHtmlRequest()) { return $this->createRestView($con fi guration, null, Response::HTTP_NO_CONTENT); } $postEventResponse = $postEvent->getResponse(); if (null !== $postEventResponse) { return $postEventResponse; } return $this->redirectHandler->redirectToIndex($con fi guration, $resource); } public function bulkDeleteAction(Request $request): Response { $con fi guration = $this->requestCon fi gurationFactory->create($this->metadata, $request); $this->isGrantedOr403($con fi guration, ResourceActions::BULK_DELETE); $resources = $this->resourcesCollectionProvider->get($con fi guration, $this->repository); if ( $con fi guration->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, $con fi guration, $resources); foreach ($resources as $resource) { $event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::DELETE, $con fi guration, $resource); if ($event->isStopped() && !$con fi guration->isHtmlRequest()) { throw new HttpException($event->getErrorCode(), $event->getMessage()); } if ($event->isStopped()) { $this-> fl ashHelper->addFlashFromEvent($con fi guration, $event); $eventResponse = $event->getResponse(); if (null !== $eventResponse) { return $eventResponse; } return $this->redirectHandler->redirectToIndex($con fi guration, $resource); } try { $this->resourceDeleteHandler->handle($resource, $this->repository); } catch (DeleteHandlingException $exception) { if (!$con fi guration->isHtmlRequest()) { return $this->createRestView($con fi guration, null, $exception->getApiResponseCode()); } $this-> fl ashHelper->addErrorFlash($con fi guration, $exception->getFlash()); return $this->redirectHandler->redirectToReferer($con fi guration); } $postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::DELETE, $con fi guration, $resource); } if (!$con fi guration->isHtmlRequest()) { return $this->createRestView($con fi guration, null, Response::HTTP_NO_CONTENT); } $this-> fl ashHelper->addSuccessFlash($con fi guration, ResourceActions::BULK_DELETE); if (isset($postEvent)) { $postEventResponse = $postEvent->getResponse(); if (null !== $postEventResponse) { return $postEventResponse; } } return $this->redirectHandler->redirectToIndex($con fi guration); } public function applyStateMachineTransitionAction(Request $request): Response { $stateMachine = $this->getStateMachine(); $con fi guration = $this->requestCon fi gurationFactory->create($this->metadata, $request); $this->isGrantedOr403($con fi guration, ResourceActions::UPDATE); $resource = $this-> fi ndOr404($con fi guration); if ($con fi guration->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, $con fi guration, $resource); if ($event->isStopped() && !$con fi guration->isHtmlRequest()) { throw new HttpException($event->getErrorCode(), $event->getMessage()); } if ($event->isStopped()) { $this-> fl ashHelper->addFlashFromEvent($con fi guration, $event); $eventResponse = $event->getResponse(); if (null !== $eventResponse) { return $eventResponse; } return $this->redirectHandler->redirectToResource($con fi guration, $resource); } if (!$stateMachine->can($con fi guration, $resource)) { throw new BadRequestHttpException(); } try { $this->resourceUpdateHandler->handle($resource, $con fi guration, $this->manager); } catch (UpdateHandlingException $exception) { if (!$con fi guration->isHtmlRequest()) { return $this->createRestView($con fi guration, $resource, $exception->getApiResponseCode()); } $this-> fl ashHelper->addErrorFlash($con fi guration, $exception->getFlash()); return $this->redirectHandler->redirectToReferer($con fi guration); } if ($con fi guration->isHtmlRequest()) { $this-> fl ashHelper->addSuccessFlash($con fi guration, ResourceActions::UPDATE, $resource); } $postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::UPDATE, $con fi guration, $resource); if (!$con fi guration->isHtmlRequest()) { if ($con fi guration->getParameters()->get('return_content', true)) { return $this->createRestView($con fi guration, $resource, Response::HTTP_OK); } return $this->createRestView($con fi guration, null, Response::HTTP_NO_CONTENT); } $postEventResponse = $postEvent->getResponse(); if (null !== $postEventResponse) { return $postEventResponse; } return $this->redirectHandler->redirectToResource($con fi guration, $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(RequestCon fi guration $con fi guration, string $permission): void { if (!$con fi guration->hasPermission()) { return; } $permission = $con fi guration->getPermission($permission); if (!$this->authorizationChecker->isGranted($con fi guration, $permission)) { throw new AccessDeniedException(); } } /** * @throws NotFoundHttpException */ protected function fi ndOr404(RequestCon fi guration $con fi guration): ResourceInterface { if (null === $resource = $this->singleResourceProvider->get($con fi guration, $this->repository)) { throw new NotFoundHttpException(sprintf('The "%s" has not been found', $this->metadata->getHumanizedName())); } return $resource; } /** * @param mixed $data */ protected function createRestView(RequestCon fi guration $con fi guration, $data, int $statusCode = null): Response { if (null === $this->viewHandler) { throw new LogicException('You can not use the "non-html" request if FriendsOfSymfony Rest Bundle is not available. Try running "composer require friendsofsymfony/rest-bundle".'); } $view = View::create($data, $statusCode); return $this->viewHandler->handle($con fi guration, $view); } protected function getStateMachine(): StateMachineInterface { if (null === $this->stateMachine) { throw new LogicException('You can not use the "state-machine" if Winzou State Machine Bundle is not available. Try running "composer require winzou/state-machine-bundle".'); } return $this->stateMachine; } } ResourceController
  • 70. <?php /* * This fi le is part of the Sylius package. * * (c) Sylius Sp. z o.o. * * For the full copyright and license information, please view the LICENSE * fi le that was distributed with this source code. */ declare(strict_types=1); namespace SyliusBundleResourceBundleController; use DoctrinePersistenceObjectManager; use FOSRestBundleViewView; use SyliusBundleResourceBundleEventResourceControllerEvent; use SyliusComponentResourceExceptionDeleteHandlingException; use SyliusComponentResourceExceptionUpdateHandlingException; use SyliusComponentResourceFactoryFactoryInterface; use SyliusComponentResourceMetadataMetadataInterface; use SyliusComponentResourceModelResourceInterface; use SyliusComponentResourceRepositoryRepositoryInterface; use SyliusComponentResourceResourceActions; 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 RequestCon fi gurationFactoryInterface $requestCon fi gurationFactory; 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 $ fl ashHelper; protected AuthorizationCheckerInterface $authorizationChecker; protected EventDispatcherInterface $eventDispatcher; protected ?StateMachineInterface $stateMachine; protected ResourceUpdateHandlerInterface $resourceUpdateHandler; protected ResourceDeleteHandlerInterface $resourceDeleteHandler; public function __construct( MetadataInterface $metadata, RequestCon fi gurationFactoryInterface $requestCon fi gurationFactory, ?ViewHandlerInterface $viewHandler, RepositoryInterface $repository, FactoryInterface $factory, NewResourceFactoryInterface $newResourceFactory, ObjectManager $manager, SingleResourceProviderInterface $singleResourceProvider, ResourcesCollectionProviderInterface $resourcesFinder, ResourceFormFactoryInterface $resourceFormFactory, RedirectHandlerInterface $redirectHandler, FlashHelperInterface $ fl ashHelper, AuthorizationCheckerInterface $authorizationChecker, EventDispatcherInterface $eventDispatcher, ?StateMachineInterface $stateMachine, ResourceUpdateHandlerInterface $resourceUpdateHandler, ResourceDeleteHandlerInterface $resourceDeleteHandler, ) { $this->metadata = $metadata; $this->requestCon fi gurationFactory = $requestCon fi gurationFactory; $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-> fl ashHelper = $ fl ashHelper; $this->authorizationChecker = $authorizationChecker; $this->eventDispatcher = $eventDispatcher; $this->stateMachine = $stateMachine; $this->resourceUpdateHandler = $resourceUpdateHandler; $this->resourceDeleteHandler = $resourceDeleteHandler; } public function showAction(Request $request): Response { $con fi guration = $this->requestCon fi gurationFactory->create($this->metadata, $request); $this->isGrantedOr403($con fi guration, ResourceActions::SHOW); $resource = $this-> fi ndOr404($con fi guration); $event = $this->eventDispatcher->dispatch(ResourceActions::SHOW, $con fi guration, $resource); $eventResponse = $event->getResponse(); if (null !== $eventResponse) { return $eventResponse; } if ($con fi guration->isHtmlRequest()) { return $this->render($con fi guration->getTemplate(ResourceActions::SHOW . '.html'), [ 'con fi guration' => $con fi guration, 'metadata' => $this->metadata, 'resource' => $resource, $this->metadata->getName() => $resource, ]); } return $this->createRestView($con fi guration, $resource); } public function indexAction(Request $request): Response { $con fi guration = $this->requestCon fi gurationFactory->create($this->metadata, $request); $this->isGrantedOr403($con fi guration, ResourceActions::INDEX); $resources = $this->resourcesCollectionProvider->get($con fi guration, $this->repository); $event = $this->eventDispatcher->dispatchMultiple(ResourceActions::INDEX, $con fi guration, $resources); $eventResponse = $event->getResponse(); if (null !== $eventResponse) { return $eventResponse; } if ($con fi guration->isHtmlRequest()) { return $this->render($con fi guration->getTemplate(ResourceActions::INDEX . '.html'), [ 'con fi guration' => $con fi guration, 'metadata' => $this->metadata, 'resources' => $resources, $this->metadata->getPluralName() => $resources, ]); } return $this->createRestView($con fi guration, $resources); } public function createAction(Request $request): Response { $con fi guration = $this->requestCon fi gurationFactory->create($this->metadata, $request); $this->isGrantedOr403($con fi guration, ResourceActions::CREATE); $newResource = $this->newResourceFactory->create($con fi guration, $this->factory); $form = $this->resourceFormFactory->create($con fi guration, $newResource); $form->handleRequest($request); if ($request->isMethod('POST') && $form->isSubmitted() && $form->isValid()) { $newResource = $form->getData(); $event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::CREATE, $con fi guration, $newResource); if ($event->isStopped() && !$con fi guration->isHtmlRequest()) { throw new HttpException($event->getErrorCode(), $event->getMessage()); } if ($event->isStopped()) { $this-> fl ashHelper->addFlashFromEvent($con fi guration, $event); $eventResponse = $event->getResponse(); if (null !== $eventResponse) { return $eventResponse; } return $this->redirectHandler->redirectToIndex($con fi guration, $newResource); } if ($con fi guration->hasStateMachine()) { $stateMachine = $this->getStateMachine(); $stateMachine->apply($con fi guration, $newResource); } $this->repository->add($newResource); if ($con fi guration->isHtmlRequest()) { $this-> fl ashHelper->addSuccessFlash($con fi guration, ResourceActions::CREATE, $newResource); } $postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::CREATE, $con fi guration, $newResource); if (!$con fi guration->isHtmlRequest()) { return $this->createRestView($con fi guration, $newResource, Response::HTTP_CREATED); } $postEventResponse = $postEvent->getResponse(); if (null !== $postEventResponse) { return $postEventResponse; } return $this->redirectHandler->redirectToResource($con fi guration, $newResource); } if ($request->isMethod('POST') && $form->isSubmitted() && !$form->isValid()) { $responseCode = Response::HTTP_UNPROCESSABLE_ENTITY; } if (!$con fi guration->isHtmlRequest()) { return $this->createRestView($con fi guration, $form, Response::HTTP_BAD_REQUEST); } $initializeEvent = $this->eventDispatcher->dispatchInitializeEvent(ResourceActions::CREATE, $con fi guration, $newResource); $initializeEventResponse = $initializeEvent->getResponse(); if (null !== $initializeEventResponse) { return $initializeEventResponse; } return $this->render($con fi guration->getTemplate(ResourceActions::CREATE . '.html'), [ 'con fi guration' => $con fi guration, 'metadata' => $this->metadata, 'resource' => $newResource, $this->metadata->getName() => $newResource, 'form' => $form->createView(), ], null, $responseCode ?? Response::HTTP_OK); } public function updateAction(Request $request): Response { $con fi guration = $this->requestCon fi gurationFactory->create($this->metadata, $request); $this->isGrantedOr403($con fi guration, ResourceActions::UPDATE); $resource = $this-> fi ndOr404($con fi guration); $form = $this->resourceFormFactory->create($con fi guration, $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, $con fi guration, $resource); if ($event->isStopped() && !$con fi guration->isHtmlRequest()) { throw new HttpException($event->getErrorCode(), $event->getMessage()); } if ($event->isStopped()) { $this-> fl ashHelper->addFlashFromEvent($con fi guration, $event); $eventResponse = $event->getResponse(); if (null !== $eventResponse) { return $eventResponse; } return $this->redirectHandler->redirectToResource($con fi guration, $resource); } try { $this->resourceUpdateHandler->handle($resource, $con fi guration, $this->manager); } catch (UpdateHandlingException $exception) { if (!$con fi guration->isHtmlRequest()) { return $this->createRestView($con fi guration, $form, $exception->getApiResponseCode()); } $this-> fl ashHelper->addErrorFlash($con fi guration, $exception->getFlash()); return $this->redirectHandler->redirectToReferer($con fi guration); } if ($con fi guration->isHtmlRequest()) { $this-> fl ashHelper->addSuccessFlash($con fi guration, ResourceActions::UPDATE, $resource); } $postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::UPDATE, $con fi guration, $resource); if (!$con fi guration->isHtmlRequest()) { if ($con fi guration->getParameters()->get('return_content', false)) { return $this->createRestView($con fi guration, $resource, Response::HTTP_OK); } return $this->createRestView($con fi guration, null, Response::HTTP_NO_CONTENT); } $postEventResponse = $postEvent->getResponse(); if (null !== $postEventResponse) { return $postEventResponse; } return $this->redirectHandler->redirectToResource($con fi guration, $resource); } if (in_array($request->getMethod(), ['POST', 'PUT', 'PATCH'], true) && $form->isSubmitted() && !$form->isValid()) { $responseCode = Response::HTTP_UNPROCESSABLE_ENTITY; } if (!$con fi guration->isHtmlRequest()) { return $this->createRestView($con fi guration, $form, Response::HTTP_BAD_REQUEST); } $initializeEvent = $this->eventDispatcher->dispatchInitializeEvent(ResourceActions::UPDATE, $con fi guration, $resource); $initializeEventResponse = $initializeEvent->getResponse(); if (null !== $initializeEventResponse) { return $initializeEventResponse; } return $this->render($con fi guration->getTemplate(ResourceActions::UPDATE . '.html'), [ 'con fi guration' => $con fi guration, 'metadata' => $this->metadata, 'resource' => $resource, $this->metadata->getName() => $resource, 'form' => $form->createView(), ], null, $responseCode ?? Response::HTTP_OK); } public function deleteAction(Request $request): Response { $con fi guration = $this->requestCon fi gurationFactory->create($this->metadata, $request); $this->isGrantedOr403($con fi guration, ResourceActions::DELETE); $resource = $this-> fi ndOr404($con fi guration); if ($con fi guration->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, $con fi guration, $resource); if ($event->isStopped() && !$con fi guration->isHtmlRequest()) { throw new HttpException($event->getErrorCode(), $event->getMessage()); } if ($event->isStopped()) { $this-> fl ashHelper->addFlashFromEvent($con fi guration, $event); $eventResponse = $event->getResponse(); if (null !== $eventResponse) { return $eventResponse; } return $this->redirectHandler->redirectToIndex($con fi guration, $resource); } try { $this->resourceDeleteHandler->handle($resource, $this->repository); } catch (DeleteHandlingException $exception) { if (!$con fi guration->isHtmlRequest()) { return $this->createRestView($con fi guration, null, $exception->getApiResponseCode()); } $this-> fl ashHelper->addErrorFlash($con fi guration, $exception->getFlash()); return $this->redirectHandler->redirectToReferer($con fi guration); } if ($con fi guration->isHtmlRequest()) { $this-> fl ashHelper->addSuccessFlash($con fi guration, ResourceActions::DELETE, $resource); } $postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::DELETE, $con fi guration, $resource); if (!$con fi guration->isHtmlRequest()) { return $this->createRestView($con fi guration, null, Response::HTTP_NO_CONTENT); } $postEventResponse = $postEvent->getResponse(); if (null !== $postEventResponse) { return $postEventResponse; } return $this->redirectHandler->redirectToIndex($con fi guration, $resource); } public function bulkDeleteAction(Request $request): Response { $con fi guration = $this->requestCon fi gurationFactory->create($this->metadata, $request); $this->isGrantedOr403($con fi guration, ResourceActions::BULK_DELETE); $resources = $this->resourcesCollectionProvider->get($con fi guration, $this->repository); if ( $con fi guration->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, $con fi guration, $resources); foreach ($resources as $resource) { $event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::DELETE, $con fi guration, $resource); if ($event->isStopped() && !$con fi guration->isHtmlRequest()) { throw new HttpException($event->getErrorCode(), $event->getMessage()); } if ($event->isStopped()) { $this-> fl ashHelper->addFlashFromEvent($con fi guration, $event); $eventResponse = $event->getResponse(); if (null !== $eventResponse) { return $eventResponse; } return $this->redirectHandler->redirectToIndex($con fi guration, $resource); } try { $this->resourceDeleteHandler->handle($resource, $this->repository); } catch (DeleteHandlingException $exception) { if (!$con fi guration->isHtmlRequest()) { return $this->createRestView($con fi guration, null, $exception->getApiResponseCode()); } $this-> fl ashHelper->addErrorFlash($con fi guration, $exception->getFlash()); return $this->redirectHandler->redirectToReferer($con fi guration); } $postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::DELETE, $con fi guration, $resource); } if (!$con fi guration->isHtmlRequest()) { return $this->createRestView($con fi guration, null, Response::HTTP_NO_CONTENT); } $this-> fl ashHelper->addSuccessFlash($con fi guration, ResourceActions::BULK_DELETE); if (isset($postEvent)) { $postEventResponse = $postEvent->getResponse(); if (null !== $postEventResponse) { return $postEventResponse; } } return $this->redirectHandler->redirectToIndex($con fi guration); } public function applyStateMachineTransitionAction(Request $request): Response { $stateMachine = $this->getStateMachine(); $con fi guration = $this->requestCon fi gurationFactory->create($this->metadata, $request); $this->isGrantedOr403($con fi guration, ResourceActions::UPDATE); $resource = $this-> fi ndOr404($con fi guration); if ($con fi guration->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, $con fi guration, $resource); if ($event->isStopped() && !$con fi guration->isHtmlRequest()) { throw new HttpException($event->getErrorCode(), $event->getMessage()); } if ($event->isStopped()) { $this-> fl ashHelper->addFlashFromEvent($con fi guration, $event); $eventResponse = $event->getResponse(); if (null !== $eventResponse) { return $eventResponse; } return $this->redirectHandler->redirectToResource($con fi guration, $resource); } if (!$stateMachine->can($con fi guration, $resource)) { throw new BadRequestHttpException(); } try { $this->resourceUpdateHandler->handle($resource, $con fi guration, $this->manager); } catch (UpdateHandlingException $exception) { if (!$con fi guration->isHtmlRequest()) { return $this->createRestView($con fi guration, $resource, $exception->getApiResponseCode()); } $this-> fl ashHelper->addErrorFlash($con fi guration, $exception->getFlash()); return $this->redirectHandler->redirectToReferer($con fi guration); } if ($con fi guration->isHtmlRequest()) { $this-> fl ashHelper->addSuccessFlash($con fi guration, ResourceActions::UPDATE, $resource); } $postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::UPDATE, $con fi guration, $resource); if (!$con fi guration->isHtmlRequest()) { if ($con fi guration->getParameters()->get('return_content', true)) { return $this->createRestView($con fi guration, $resource, Response::HTTP_OK); } return $this->createRestView($con fi guration, null, Response::HTTP_NO_CONTENT); } $postEventResponse = $postEvent->getResponse(); if (null !== $postEventResponse) { return $postEventResponse; } return $this->redirectHandler->redirectToResource($con fi guration, $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(RequestCon fi guration $con fi guration, string $permission): void { if (!$con fi guration->hasPermission()) { return; } $permission = $con fi guration->getPermission($permission); if (!$this->authorizationChecker->isGranted($con fi guration, $permission)) { throw new AccessDeniedException(); } } /** * @throws NotFoundHttpException */ protected function fi ndOr404(RequestCon fi guration $con fi guration): ResourceInterface { if (null === $resource = $this->singleResourceProvider->get($con fi guration, $this->repository)) { throw new NotFoundHttpException(sprintf('The "%s" has not been found', $this->metadata->getHumanizedName())); } return $resource; } /** * @param mixed $data */ protected function createRestView(RequestCon fi guration $con fi guration, $data, int $statusCode = null): Response { if (null === $this->viewHandler) { throw new LogicException('You can not use the "non-html" request if FriendsOfSymfony Rest Bundle is not available. Try running "composer require friendsofsymfony/rest-bundle".'); } $view = View::create($data, $statusCode); return $this->viewHandler->handle($con fi guration, $view); } protected function getStateMachine(): StateMachineInterface { if (null === $this->stateMachine) { throw new LogicException('You can not use the "state-machine" if Winzou State Machine Bundle is not available. Try running "composer require winzou/state-machine-bundle".'); } return $this->stateMachine; } } ResourceController
  • 71. !
  • 72. interface ProviderInterface { public function provide(Operation $operation, Context $context): object|iterable|null; } interface ProcessorInterface { public function process(mixed $data, Operation $operation, Context $context): mixed; }
  • 74. $grid->addGrid(GridBuilder::create('app_user', '%app.model.user.class%') ->setLimits([10, 25, 50, 100]) ->addField( Field::create('name', 'twig') ->setLabel('Name') ->setSortable(true) ) ->addFilter( Filter::create('name', 'string') ->setLabel('app.ui.name') ->setEnabled(true) ->setFormOptions(['type' => 'contains']) ) ->addActionGroup(MainActionGroup::create( Action::create('create', 'create') )) ;
  • 75. Is Sylius DX still great?…
  • 76. Is Sylius DX still great?… Of course
  • 77. Q&A