3

I need to implement syliusOrderBundle in my symfony2 apps, i have read the docs over and over again from their official site http://docs.sylius.org/en/latest/bundles/SyliusOrderBundle/installation.html i have ended up installing and enabled the following bundles

      new Sylius\Bundle\ResourceBundle\SyliusResourceBundle(),
        new Sylius\Bundle\MoneyBundle\SyliusMoneyBundle(),
        new Sylius\Bundle\OrderBundle\SyliusOrderBundle(),
        new Sylius\Bundle\CartBundle\SyliusCartBundle(),
        new Sylius\Bundle\ProductBundle\SyliusProductBundle(),
        new Sylius\Bundle\ArchetypeBundle\SyliusArchetypeBundle(),
        new Sylius\Bundle\AttributeBundle\SyliusAttributeBundle(),
        new Sylius\Bundle\AssociationBundle\SyliusAssociationBundle(),
        new Sylius\Bundle\VariationBundle\SyliusVariationBundle(),

the problem is am not comfortable installing unnecessary bundles due to security reasons and the sylius doc is not helping. all i need is to be able to add product to orders pls if you have used it before can you help. THANKS

Adam Elsodaney
  • 7,722
  • 6
  • 39
  • 65
Akoh Victor Gutz
  • 620
  • 7
  • 25
  • 1
    Do you need just _Orders_ or do you need _Products_ and _Orders_? Sylius is not so much decoupled but attempts to layer its architecture, so some components and bundles will depend on each other. For example, _Products_ are objects which are subject to _Archetypes_ that define what _Attributes_ and _Variations_ an object has. – Adam Elsodaney Mar 09 '16 at 16:21
  • 1
    @adam I need only orders i have my custom product entity which serve my purpose but the documentation for orders requested that i should implement product interface http://docs.sylius.org/en/latest/bundles/SyliusOrderBundle/installation.html – Akoh Victor Gutz Mar 10 '16 at 08:58
  • 1
    All you need to do is making your Product entity to implement ProductInterface and configure it inside Symfony settings.` // src/App/AppBundle/Entity/Product.php namespace App\AppBundle\Entity; use Sylius\Component\Product\Model\ProductInterface; class Product implements ProductInterface { public function getName() { return $this->name; } }` Now, you do not even have to map your Product model to the order items. It is all done automatically. And that would be all about entities. – Akoh Victor Gutz Mar 10 '16 at 09:01
  • 1
    Oh crap, that specific page is extremely outdated (May 2014) :/ - at that time Order and Product were part of the same bundle, but now they're decoupled. If you give me a moment, I'll write up an answer on how to use both the Order component and OrderBundle with the current version (0.17)... and maybe submit a pull request to Sylius-Docs – Adam Elsodaney Mar 10 '16 at 09:16
  • @Adam That would be really great man i would be so happy i have been on this task for the past 3 days and its really frustrating. THANKS AM WAITING – Akoh Victor Gutz Mar 10 '16 at 15:47
  • no worries. I see if I can do something by the weekend, which I'll be doing on this repo here: https://github.com/adamelso/orda – Adam Elsodaney Mar 11 '16 at 09:11

1 Answers1

5

First, all you need is sylius/order-bundle version 0.17 (at time of writing)

$ composer require sylius/order-bundle "^0.17"

Then add the necessary bundles to app/AppKernel.php:

public function registerBundles()
{
    $bundles = [
        // Bundles you've already registered go here.

        // The following bundles are dependencies of Sylius ResourceBundle.
        new FOS\RestBundle\FOSRestBundle(),
        new JMS\SerializerBundle\JMSSerializerBundle($this),
        new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),
        new WhiteOctober\PagerfantaBundle\WhiteOctoberPagerfantaBundle(),

        // The following Sylius bundles are dependencies of Sylius OrderBundle 
        new Sylius\Bundle\ResourceBundle\SyliusResourceBundle(),
        new Sylius\Bundle\MoneyBundle\SyliusMoneyBundle(),
        new Sylius\Bundle\SequenceBundle\SyliusSequenceBundle(),

        new Sylius\Bundle\OrderBundle\SyliusOrderBundle(),

        // Doctrine bundle MUST be the last bundle registered.

        new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
    ];

    //....

    return $bundles;
}

Now to use the OrderBundle with your own entities you need to create an Order and OrderItem entity that extend the ones provided by Sylius.

For Order, we'll add a field to capture the visitor's email address. You could capture the current user, or whatever you like to identify who made the order.

<?php

namespace AppBundle\Entity;

use Sylius\Component\Order\Model\Order as SyliusOrder;

class Order extends SyliusOrder
{
    /**
     * @var string
     */
    private $email;

    /**
     * @return string
     */
    public function getEmail()
    {
        return $this->email;
    }

    /**
     * @param string $email
     */
    public function setEmail($email)
    {
        $this->email = $email;
    }
}

For the purpose of this demonstration, we'll assume visitors want to order music tracks for download.

Otherwise, this can be anything. In the Sylius app, it's a Product. In some apps it might not be a product but a subscription or a service, etc.

So for OrderItem, we'll add a field to capture the downloads the visitor will order.

<?php

namespace AppBundle\Entity;

use Sylius\Component\Order\Model\OrderItem as SyliusOrderItem;

class OrderItem extends SyliusOrderItem
{
    /**
     * @var Download
     */
    private $download;

    /**
     * @return Download
     */
    public function getDownload()
    {
        return $this->download;
    }

    /**
     * @param Download $download
     */
    public function setDownload(Download $download)
    {
        $this->download = $download;
    }
}

Now you have your entities, you can add the Doctrine mapping. In this example, we're using XML (ugly, but it validates the config) but it can be YAML or annotations.

In src/AppBundle/Resources/config/doctrine/Order.orm.xml:

<?xml version="1.0" encoding="UTF-8"?>

<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
        http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

<entity name="AppBundle\Entity\Order" table="app_order">
    <field name="email" column="email" type="string" nullable="true" />

    <one-to-many field="items" target-entity="Sylius\Component\Order\Model\OrderItemInterface" mapped-by="order" orphan-removal="true">
        <cascade>
            <cascade-all/>
        </cascade>
    </one-to-many>

    <one-to-many field="adjustments" target-entity="Sylius\Component\Order\Model\AdjustmentInterface" mapped-by="order" orphan-removal="true">
        <cascade>
            <cascade-all/>
        </cascade>
    </one-to-many>
</entity>

In src/AppBundle/Resources/config/doctrine/OrderItem.orm.xml:

<?xml version="1.0" encoding="UTF-8"?>

<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
         http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

    <entity name="AppBundle\Entity\OrderItem" table="app_order_item">
        <many-to-one field="download" target-entity="AppBundle\Entity\Download">
            <join-column name="download_id" referenced-column-name="id" nullable="false" />
        </many-to-one>

    </entity>

</doctrine-mapping>

Then we need to let the Sylius OrderBundle know about our new entities so that it doesn't use its default ones.

Also we want to generate order numbers for our Order entity, which is done by the Sylius SequenceBundle.

In app/config/config.yml, add this configuration:

sylius_sequence:
    generators:
        AppBundle\Entity\Order: sylius.sequence.sequential_number_generator

sylius_order:
    resources:
        order:
            classes:
                model: AppBundle\Entity\Order
        order_item:
            classes:
                model: AppBundle\Entity\OrderItem

Then update your database schema:

$ php app/console doctrine:schema:update --dump-sql --force

Now for order numbers to actually get generated and assigned to the Order when a new entity is persisted, we need to register a listener.

In app/config/services.yml OR in your bundle's Resources/config/services.yml, add this configuration:

parameters:
  sylius.model.sequence.class: Sylius\Component\Sequence\Model\Sequence

services:
  app.order_number_listener:
    class: Sylius\Bundle\OrderBundle\EventListener\OrderNumberListener
    arguments:
      - "@sylius.sequence.doctrine_number_listener"
    tags:
      - { name: kernel.event_listener, event: app.download_ordered, method: generateOrderNumber }

The name of the event app.download_ordered is important here, you can name this anything you wish, but it must be dispatched when you create a new order.

Here is an example of creating a new order, where we dispatch the app.download_ordered event.

    use AppBundle\Entity\Download;
    use AppBundle\Entity\OrderItem;
    use AppBundle\Form\OrderType;
    use Sensio\Bundle\FrameworkExtraBundle\Configuration as Framework;
    use AppBundle\Entity\Order;
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    use Symfony\Component\EventDispatcher\GenericEvent;
    use Symfony\Component\HttpFoundation\Request;

    const STATE_BEGIN    = 'begin';
    const STATE_COMPLETE = 'complete';

    /**
     * @Framework\Route("/begin-order-for-download/{id}", name="begin_order_for_download")
     */
    public function beginOrderForDownloadAction(Request $request, $id)
    {
        $download = $this->getDoctrine()->getRepository(Download::class)->findOneBy(['id' => $id]);

        $order = new Order();
        $form = $this->createForm(new OrderType(), $order);

        if ('POST' === $request->getMethod()) {
            $order->setState(self::STATE_BEGIN);

            $orderItem = new OrderItem();
            $orderItem->setDownload($download);
            $orderItem->setOrder($order);
            $orderItem->setUnitPrice(59);
            // $orderItem->setImmutable(true); // Need to verify how this affects behavior.

            $this->get('event_dispatcher')->dispatch('app.download_ordered', new GenericEvent($order));

            $form->handleRequest($request);

            $em = $this->getDoctrine()->getManager();

            if ($form->isValid()) {
                $order->setState(self::STATE_COMPLETE);
                $this->addFlash('order.state', self::STATE_COMPLETE);

                $em->persist($order);
                $em->flush();

                return $this->redirectToRoute('complete_order', [
                    'id' => $order->getId(),
                ]);
            }
        }

        return $this->render('AppBundle::begin_order_for_download.html.twig', [
            'form'     => $form->createView(),
            'download' => $download,
        ]);
    }

The OrderType form looks like this, kept simple for demonstration purposes. Edit to your needs.

    <?php

    namespace AppBundle\Form;

    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\FormBuilderInterface;

    class OrderType extends AbstractType
    {
        /**
         * {@inheritdoc}
         */
        public function getName()
        {
            return 'order';
        }

        /**
         * {@inheritdoc}
         */
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            $builder->add('email', 'repeated', [
                'type'            => 'email',
                'first_options'   => ['label' => 'Email'],
                'second_options'  => ['label' => 'Repeat Email'],
                'invalid_message' => 'The email fields must match.',
            ]);
            $builder->add('proceed', 'submit', ['attr' => ['class' => 'button']]);
        }
    }

If the form was valid, our order would be persisted and the listener assign it an order number, then we would be rediect complete_order route. Here you'd do things like provide payment detials or in this case, provide a link to download the song.

That's it. You can see the code for the full working example at https://github.com/adamelso/orda

Adam Elsodaney
  • 7,722
  • 6
  • 39
  • 65