2. About me
2
− 20+ years of experience
− 3+ years at Spryker
− Project experience with various stack
− Project experience with multiple industries
− ...
3. What will we learn?
3
− How to place an Order?
− What is a step engine?
− Why is do we need step engine?
− How to implements a new step?
− Where do we modify the Quote?
− What happens it something goes wrong?
− ...
6. − Step is a class that implements the StepInterface and handles the data.
What is a step?
6
7. StepInterface
7
public function preCondition(AbstractTransfer $quoteTransfer);
public function requireInput(AbstractTransfer $dataTransfer);
public function execute(Request $request, AbstractTransfer $dataTransfer);
public function postCondition(AbstractTransfer $dataTransfer);
public function getStepRoute();
public function getEscapeRoute();
public function getTemplateVariables(AbstractTransfer $dataTransfer);
8. preCondition method
8
Preconditions are called before each step. If the condition is met then it will return true and the process
will continue. If it returns false, the customer will be redirected to the escape route defined for this step.
/**
* @param GeneratedSharedTransferQuoteTransfer $quoteTransfer
*
* @return bool
*/
public function preCondition(AbstractTransfer $quoteTransfer)
{
if ($this->isCartEmpty($quoteTransfer)) {
return false;
}
if (!$quoteTransfer->getCheckoutConfirmed()) {
$this->escapeRoute = CheckoutPageControllerProvider::CHECKOUT_SUMMARY;
return false;
}
return true;
}
9. requireInput method
9
In this method we determine if step requires user input. If input methods return true we will render the
form and wait for user input.
/**
* Require input for customer authentication if the customer is not logged in already, or haven't authenticated yet.
*
* @param GeneratedSharedTransferQuoteTransfer $quoteTransfer
*
* @return bool
*/
public function requireInput(AbstractTransfer $quoteTransfer)
{
if ($this->isCustomerLoggedIn()) {
return false;
}
return true;
}
10. execute method
10
If the form is valid, the updated QuoteTransfer is passed to Step::execute() method where you can
modify it further or apply custom logic (call a client or Zed). Also, there is Symfony Request object
passed if additional/manual data mapping is required.
/**
* Update QuoteTransfer with customer step handler plugin.
*
* @param SymfonyComponentHttpFoundationRequest $request
* @param GeneratedSharedTransferQuoteTransfer $quoteTransfer
*
* @return GeneratedSharedTransferQuoteTransfer
*/
public function execute(Request $request, AbstractTransfer $quoteTransfer)
{
return $this->customerStepHandler->addToDataClass($request, $quoteTransfer);
}
11. postCondition method
11
Post condition is an essential part of the step Processing. It indicates if a step has all the data that it
needs and if its requirements are satisfied.
/**
* @param GeneratedSharedTransferQuoteTransfer $quoteTransfer
*
* @return bool
*/
public function postCondition(AbstractTransfer $quoteTransfer)
{
if ($this->hasOnlyGiftCardItems($quoteTransfer)) {
return true;
}
if (!$this->isShipmentSet($quoteTransfer)) {
return false;
}
return true;
}
Do not modify the Quote in postCondition!!!
12. getTemplateVariables method
12
With this method we can provide additional parameters that we might want to render on the template.
/**
* @param GeneratedSharedTransferQuoteTransfer $quoteTransfer
*
* @return array
*/
public function getTemplateVariables(AbstractTransfer $quoteTransfer)
{
return [
'quoteTransfer' => $quoteTransfer,
'cartItems' => $this->productBundleClient->getGroupedBundleItems(
$quoteTransfer->getItems(),
$quoteTransfer->getBundleItems()
),
];
}
13. getStepRoute and getEscapeRoute methods
13
Step route is the route to the step and
escape route is where we redirect in case of an error.
/**
* @return string
*/
public function getStepRoute()
{
return $this->stepRoute;
}
/**
* @return string
*/
public function getEscapeRoute()
{
return $this->escapeRoute;
}
15. interface StepCollectionInterface extends IteratorAggregate
{
public function addStep(StepInterface $step);
public function canAccessStep(StepInterface $step, Request $request, AbstractTransfer $dataTransfer);
public function getCurrentStep(Request $request, AbstractTransfer $dataTransfer);
public function getNextStep(StepInterface $currentStep);
public function getPreviousStep(StepInterface $currentStep, ?AbstractTransfer $dataTransfer = null);
public function getCurrentUrl(StepInterface $currentStep);
public function getNextUrl(StepInterface $currentStep, AbstractTransfer $dataTransfer);
public function getPreviousUrl(StepInterface $currentStep, ?AbstractTransfer $dataTransfer = null);
public function getEscapeUrl(StepInterface $currentStep);
}
Step collection
15
Step engine accesses step through step engine. Order of steps is also defined by collection.
16. canAccessStep method
16
With this method we determine if we can access a step or not. We can access it only if previous steps
were completed successfully.
/**
* @param SprykerYvesStepEngineDependencyStepStepInterface $currentStep
* @param SymfonyComponentHttpFoundationRequest $request
* @param GeneratedSharedTransferQuoteTransfer $dataTransfer
*
* @return bool
*/
public function canAccessStep(StepInterface $currentStep, Request $request, AbstractTransfer $dataTransfer)
{
if ($request->get('_route') === $currentStep->getStepRoute()) {
return true;
}
foreach ($this->getCompletedSteps($dataTransfer) as $step) {
if ($step->getStepRoute() === $request->get('_route')) {
return true;
}
}
return false;
}
17. getCurrentStep method
17
This method will return the last not completed step by checking the post condition of steps.
/**
* @param SymfonyComponentHttpFoundationRequest $request
* @param GeneratedSharedTransferQuoteTransfer $dataTransfer
*
* @return SprykerYvesStepEngineDependencyStepStepInterface
*/
public function getCurrentStep(Request $request, AbstractTransfer $dataTransfer)
{
foreach ($this->steps as $step) {
if (!$step->postCondition($dataTransfer) || $request->get('_route') === $step->getStepRoute()) {
return $step;
}
$this->completedSteps[] = $step;
}
return end($this->completedSteps);
}
19. Form collection
19
This class is responsible for forms rendering and data binding.
interface FormCollectionHandlerInterface
{
public function getForms(AbstractTransfer $dataTransfer);
public function hasSubmittedForm(Request $request, AbstractTransfer $dataTransfer);
public function handleRequest(Request $request, AbstractTransfer $dataTransfer);
public function provideDefaultFormData(AbstractTransfer $dataTransfer);
}
20. handleRequest method
20
After form has been submitted we validate the input and populate the underlying data object.
public function handleRequest(Request $request, AbstractTransfer $dataTransfer)
{
foreach ($this->getForms($dataTransfer) as $form) {
if ($request->request->has($form->getName())) {
$form->setData($dataTransfer);
return $form->handleRequest($request);
}
}
throw new InvalidFormHandleRequest('Form to handle not found in Request.');
}
21. data Providers
21
If we need to provide additional data to the form we use dataProviders
interface StepEngineFormDataProviderInterface
{
/**
* @param GeneratedSharedTransferQuoteTransfer $quoteTransfer
*
* @return GeneratedSharedTransferQuoteTransfer
*/
public function getData(AbstractTransfer $quoteTransfer);
/**
* @param GeneratedSharedTransferQuoteTransfer $quoteTransfer
*
* @return array
*/
public function getOptions(AbstractTransfer $quoteTransfer);
}
22. getData method
22
In this method we can do a some business logic before setting values to quote.
/**
* @param GeneratedSharedTransferQuoteTransfer $quoteTransfer
*
* @return SprykerSharedKernelTransferAbstractTransfer
*/
public function getData(AbstractTransfer $quoteTransfer)
{
$quoteTransfer->setShippingAddress($this->getShippingAddress($quoteTransfer));
$quoteTransfer->setBillingAddress($this->getBillingAddress($quoteTransfer));
$quoteTransfer->setBillingSameAsShipping(
$this->isSameAddress($quoteTransfer->getShippingAddress(), $quoteTransfer->getBillingAddress())
);
return $quoteTransfer;
}
23. getOptions
23
If we need to populate dropdowns (call Zed) or have business rules that we need to apply then we do it
in getOptions
/**
* @param GeneratedSharedTransferQuoteTransfer $quoteTransfer
*
* @return array
*/
public function getOptions(AbstractTransfer $quoteTransfer)
{
return [
CheckoutAddressCollectionForm::OPTION_ADDRESS_CHOICES => $this->getAddressChoices(),
CheckoutAddressCollectionForm::OPTION_COUNTRY_CHOICES => $this->getAvailableCountries(),
];
}
25. dataContainer
25
DataContainer is a simple wrapper around quote persistence. Depending on the selected strategy it
can be either Redis or DB (or you can have your own custom implementation)
interface DataContainerInterface
{
/**
* @return GeneratedSharedTransferQuoteTransfer
*/
public function get();
/**
* @param GeneratedSharedTransferQuoteTransfer $quoteTransfer
*
* @return void
*/
public function set(AbstractTransfer $quoteTransfer);
}
32. Checkout workflow
32
interface CheckoutWorkflowInterface
{
/**
* @param GeneratedSharedTransferQuoteTransfer $quoteTransfer
*
* @return GeneratedSharedTransferCheckoutResponseTransfer
*/
public function placeOrder(QuoteTransfer $quoteTransfer);
}
After we are through the checkout we still need to place the order. We need to transform Quote into Order
and store it into database. That is done in Checkout workflow on Zed.
33. Place the order
33
public function placeOrder(QuoteTransfer $quoteTransfer)
{
$checkoutResponseTransfer = $this->createCheckoutResponseTransfer();
if (!$this->checkPreConditions($quoteTransfer, $checkoutResponseTransfer)) {
return $checkoutResponseTransfer;
}
$quoteTransfer = $this->doPreSave($quoteTransfer, $checkoutResponseTransfer);
$quoteTransfer = $this->doSaveOrder($quoteTransfer, $checkoutResponseTransfer);
$this->runStateMachine($checkoutResponseTransfer->getSaveOrder());
$this->doPostSave($quoteTransfer, $checkoutResponseTransfer);
return $checkoutResponseTransfer;
}
− check preconditions (is quote still valid)
− presave (any additional info)
− saving the order
− post save
You only have 30s !!!
34. checkCondition method
34
interface CheckoutPreConditionPluginInterface
{
/**
* Specification:
* - Checks a condition before the order is saved.
* - Returns "false" and adds error to response transfer when condition failso.
* - Returns "true" when condition passed. Can add errors to response transfer.
* - Does not change Quote transfer.
* - Does not write to Persistence.
*
* @api
*
* @param GeneratedSharedTransferQuoteTransfer $quoteTransfer
* @param GeneratedSharedTransferCheckoutResponseTransfer $checkoutResponseTransfer
*
* @return bool
*/
public function checkCondition(QuoteTransfer $quoteTransfer, CheckoutResponseTransfer
$checkoutResponseTransfer);
}
check if Quote is still valid
35. preSave method
35
interface CheckoutPreSaveInterface
{
/**
* Specification:
* - Do something before orderTransfer save
*
* @api
*
* @param GeneratedSharedTransferQuoteTransfer $quoteTransfer
*
* @return GeneratedSharedTransferQuoteTransfer
*/
public function preSave(QuoteTransfer $quoteTransfer);
}
expand the quote with additional info
36. saveOrder method
36
interface CheckoutDoSaveOrderInterface
{
/**
* Specification:
* - Retrieves (its) data from the quote object and saves it to the database.
* - These plugins are already enveloped into a transaction.
*
* @api
*
* @param GeneratedSharedTransferQuoteTransfer $quoteTransfer
* @param GeneratedSharedTransferSaveOrderTransfer $saveOrderTransfer
*
* @return void
*/
public function saveOrder(QuoteTransfer $quoteTransfer, SaveOrderTransfer $saveOrderTransfer);
}
persist checked and expanded Quote to database - place an order.
37. postSave method
37
interface CheckoutPostSaveHookInterface
{
/**
* Specification:
* - This plugin is called after the order is placed.
* - Set the success flag to false, if redirect should be headed to an error page afterwords
*
* @api
*
* @param GeneratedSharedTransferQuoteTransfer $quoteTransfer
* @param GeneratedSharedTransferCheckoutResponseTransfer $checkoutResponse
*
* @return void
*/
public function executeHook(QuoteTransfer $quoteTransfer, CheckoutResponseTransfer
$checkoutResponse);
}
if we need to make an action after order is placed.
39. 39
The Spryker Commerce
OS is a “beyond shop -
beyond desktop”
commerce technology,
enabling transactional
use cases at every
touchpoint today and in
future.