API Platform is the most advanced API platform, in any framework or language.
CRUD
Validation
Filtering
Sorting
Pagination
Authentication
- 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
Security
JSON-LD
Hypermedia
GraphQL
Cache
Open API
Admin panel
Generators
Production ready
- 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.
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.
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.
composer require symfony/messenger
1st step require components
- 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
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
- configuring 2 messages with their own transports - now only amqp - Redis (symfony pr)
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
For this step create controller that calls transitions on the workflow
/**
* 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;
}
}
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