Leverage the power of
Symfony within
spidey test1
Antoine Bluchet - soyuka








~260 repositories


API Platform is the most advanced API platform, in any framework or language.

Fabien Potencier (creator of Symfony), SymfonyCon 2017




- CRUD: Creating, retrieving, updating and deleting - Hypermedia As The Engine Of Application State (HATEOAS): gives informations whithin the retrieval of resources, for example links between resources. - Symfony: Compatible with most existing symfony bundles
- Standard edition is based on docker and has everything you need to build production-ready APIs. - An admin panel built on React admin leveraging hypermedia capabilities - Tools to scaffold/generate react/redux apps in one command.
~2900 commits
👥 140 contributors
⭐ 3,5k stars
1M+ downloads
Benefits from the Symfony ecosystem - releases every 2 months - compatible with any symfony/symfony-based components - 1 067 792 installs of core
Let's order pizza 🍕 1. Client order 2. Prepare 3. Deliver
Workflow
Order 🡒 Prepare 🡒 Deliver
      🡓          🡓
Notify
    Kitchen      
Workflow
Order 🡒 Prepare 🡒 Deliver
      🡓          🡓
Notify
    Kitchen      🚚
Now for the messages we want to send messages to different parties. next step configure messenger

Symfony 4 + Api Platform = 💓

composer create-project symfony/skeleton my-api
cd my-api
composer require api
- Api platform has a recipes for symfony flex. - https://symfony.sh/ - doctrine + cors + validator + symfony/security


The Workflow component provides tools for managing a workflow or finite state machine.
https://symfony.com/doc/current/components/workflow.html




composer require symfony/workflow
1st step require components thanks to receipes you have nothing else to do, just change the configuration


The Messenger component helps applications send and receive messages to/from other applications or via message queues.
https://symfony.com/doc/current/components/messenger.html




composer require symfony/messenger
1st step require components
diagram - Left client orders - bottom api sends a message "prepare" kitchen, - pizza prepared kitchen updates the order (ie patch) - on the right, we send a "deliver" message - pizza delivered in 5
diagram
src/Entity/Order.php
/**
 * @ApiResource
 * @ORM\Entity
 * @ORM\Table(name="orders")
 */
final class Order
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column(type="string")
     */
    public $status;

    public function getId()
    {
        return $this->id;
    }
}

Then, just create an entity and add a @ApiResource annotation
- Api platform declared default operations! - next step, setup the workflow
Workflow
Order 🡒 Prepare 🡒 Deliver
      🡓          🡓
Notify
    Kitchen      
config/packages/workflow.yaml
workflow:
  places:
      - order
      - prepared
      - delivered
      - canceled
  transitions:
      prepare:
          from: order
          to: prepared
      deliver:
          from: prepared
          to: delivered
      cancel:
          from: [order, prepared]
          to: canceled
Translated to yaml (in config/workflow.yaml)
bin/console workflow:dump order
  |  dot -Tsvg -o order_graph.svg
Example integration with symfony: use the workflow:dump command
Then we want to setup messages (next slide)
Workflow
Order 🡒 Prepare 🡒 Deliver
      🡓          🡓
Notify
    Kitchen      🚚
Now for the messages we want to send messages to different parties. next step configure messenger
config/packages/messenger.yaml
# MESSENGER_TRANSPORT_KITCHEN=amqp://guest:guest@localhost:5672/%2f/kitchen
framework:
    messenger:
        serializer:
                format: 'jsonld'

        transports:
            kitchen: '%env(MESSENGER_TRANSPORT_KITCHEN)%'
            delivery_truck: '%env(MESSENGER_TRANSPORT_DELIVERY_TRUCK)%'

        routing:
            'App\Message\PrepareOrderMessage': kitchen
            'App\Message\DeliverOrderMessage': delivery_truck
- configuring 2 messages with their own transports - now only amqp - Redis (symfony pr)
diagram step 2, when order gets created : dispatch prepare message
src/Entity/Order.php
/**
 * @ApiResource
 * @ORM\Entity
 * @ORM\EntityListeners({OrderCreateListener::class})
 * @ORM\Table(name="orders")
 */
final class Order
{
}
src/EventListener/OrderCreateListener.php
final class OrderCreateListener
{
    private $bus;
    public function __construct(MessageBusInterface $bus)
    {
        $this->bus = $bus;
    }

    public function postPersist(Order $order)
    {
        $this->bus->dispatch(new PrepareOrderMessage($order));
    }
}
... where message looks like: nextslide
src/Message/PrepareOrderMessage.php
final class PrepareOrderMessage
{
    public $order;

    public function __construct(Order $order) {
        $this->order = $order;
    }
}
src/MessageHandler/PrepareOrderHandler.php
final class PrepareOrderHandler
{
    public function __invoke(PrepareOrderMessage $message)
    {
        /**
         * Do things
         * Then update the order status via
         * PATCH "/api/orders/{$message->order->getId()}/prepare"
         **/
    }
}
the message handler will call PATCH /api/orders/x/prepare after processing to get to the next step
diagram For this step create controller that calls transitions on the workflow
src/Entity/Order.php
/**
 * @ApiResource(
 *   itemOperations={"get", "put", "delete",
 *     "status"={
 *       "method"="PATCH",
 *       "path"="/orders/{id}/{transition}",
 *       "controller"=OrderTransitionController::class
 *     }
 *   }
 * )
 * @ORM\Entity
 * @ORM\EntityListeners({OrderCreateListener::class})
 * @ORM\Table(name="orders")
 */
final class Order
{
}
And the controller goes... nextslide
src/Controller/OrderTransitionController.php
/**
 * PATCH /orders/{id}/{transition}
 */
final class OrderTransitionController
{
    public function __construct(Registry $workflows)
    {
        $this->workflows = $workflows;
    }

    public function __invoke(Order $data, $transition): Order
    {
        $workflow = $this->workflows->get($data);

        try {
            $workflow->apply($data, $transition);
        } catch (TransitionException $exception) {
            throw new HttpException(400, "Can not transition to $transition");
        }

        return $data;
    }
}
diagram Use workflow event listener to subscribe on the prepare transition and dispatch a deliver message
src/EventListener/OrderStateListener.php
final class OrderStateListener
{
    public function __construct(MessageBusInterface $bus)
    {
        $this->bus = $bus;
    }

    public function onPrepare(Event $event)
    {
        $order = $event->getSubject();
        $this->bus->dispatch(new DeliverOrderMessage($order));
    }

    public static function getSubscribedEvents()
    {
        return array(
            'workflow.order.transition.prepare' => 'onPrepare',
        );
    }
}
src/MessageHandler/PrepareOrderHandler.php
final class DeliverOrderHandler
{
    public function __invoke(DeliverOrderMessage $message)
    {
        /**
         * PATCH "/api/orders/{$message->order->getId()}/deliver"
         **/
    }
}
The deliver order handler handles the delivery then calls PATCH /api/orders/x and fullfills our step 5
diagram
DEMO
https://github.com/soyuka | https://twitter.com/s0yuka
git clone git@github:soyuka/
leverage-the-power-of-symfony-components-within-apiplatform