$30 off During Our Annual Pro Sale. View Details »

Symfony + React = ?

Symfony + React = ?

Immer häufiger wird das Frontend moderner Anwendungen durch ReactJS/Angular/VueJS oder andere reine JS-Frontends umgesetzt. Symfony und PHP dienen dann als reine API-getriebene Backends. Häufig kommt die Frage auf, wie eine Symfony-Anwendung, die auf diesen Use Case zugeschnitten ist, aussehen sollte. Diese Frage wird in diesem Talk versucht zu beantworten.

Denis Brumann

May 04, 2018
Tweet

More Decks by Denis Brumann

Other Decks in Programming

Transcript

  1. Denis Brumann // [email protected] // @dbrumann
    + React = ?
    1

    View Slide

  2. Denis Brumann // [email protected] // @dbrumann
    React Basics
    2

    View Slide

  3. Denis Brumann // [email protected] // @dbrumann
    import React, { Component } from 'react';
    class Checkout extends Component
    {
    constructor(props) {
    super(props);
    }
    }
    export default Checkout;
    3

    View Slide

  4. Denis Brumann // [email protected] // @dbrumann
    import React, { Component } from 'react';
    class Checkout extends Component
    {
    constructor(props) {
    super(props);
    this.state = {
    basket: {},
    shippingInfo: {},
    paymentInfo: {},
    }
    this.onCheckoutConfirm = this.onCheckoutConfirm.bind(this);
    }
    onCheckoutConfirm() {
    const {basket, shippingInfo, paymentInfo} = this.state;
    // ...
    }
    }
    export default Checkout;
    4

    View Slide

  5. Denis Brumann // [email protected] // @dbrumann
    import React, { Component } from 'react';
    class Checkout extends Component
    {
    constructor(props) {...}
    onCheckoutConfirm() {...}
    render() {
    const {basket, shippingInfo, paymentInfo} = this.state;
    return (

    ...


    );
    }
    }
    export default Checkout;
    5

    View Slide

  6. Denis Brumann // [email protected] // @dbrumann
    Lifecycle Methods
    constructor(props) Initialer state der Komponente wird gesetzt.
    render() Erzeugt den Output der Komponente.
    componentDidMount()
    Komponente wird in den DOM eingefügt.
    "If you need to load data from a remote endpoint, this 

    is a good place to instantiate the network request."
    https://reactcheatsheet.com/
    6

    View Slide

  7. Denis Brumann // [email protected] // @dbrumann
    componentDidMount() {
    const { product } = this.state;
    this.updateProduct(product);
    }
    updateProduct(product) {
    fetch('http://api.example.com/products/' + product.id)
    .then(resp => resp.json())
    .then(result => this.setProductDetails(result))
    .catch(error => error);
    }
    setProductDetails(result) {
    this.setState({
    product: { ...this.state.product, ...result }
    });
    }
    7

    View Slide

  8. Denis Brumann // [email protected] // @dbrumann
    Symfony?
    8

    View Slide

  9. Denis Brumann // [email protected] // @dbrumann
    9

    View Slide

  10. Denis Brumann // [email protected] // @dbrumann
    10

    View Slide

  11. Denis Brumann // [email protected] // @dbrumann
    11

    View Slide

  12. Denis Brumann // [email protected] // @dbrumann
    class ProductController
    {
    /**
    * @Route("/api/products")
    */
    public function listProducts()
    {
    return [
    1 => [
    'id' => 1,
    'name' => 'Awesome Product',
    'description' => 'Lorem ipsum dolor sit amet...',
    'price' => 350,
    ],
    3 => [
    'id' => 3,
    'name' => 'Cool Product',
    'description' => 'One morning, when Gregor Samsa...',
    'price' => 999,
    ],
    ];
    }
    }
    12
    !❗

    View Slide

  13. Denis Brumann // [email protected] // @dbrumann
    Request Response
    kernel.

    request
    kernel.

    response
    kernel.

    controller
    kernel.

    exception
    resolve
    controller
    call
    controller
    kernel.

    terminate
    Exception
    response?
    resolve
    arguments
    Symfony Event Cycle
    kernel.

    view
    13
    Rückgabewert wird
    als ControllerResult
    ins Event gegeben
    erzeugte Response
    wird weitergegeben

    View Slide

  14. Denis Brumann // [email protected] // @dbrumann
    class JsonViewListener implements EventSubscriberInterface
    {
    public static function getSubscribedEvents()
    {
    return [KernelEvents::VIEW => 'transformViewData'];
    }
    public function transformViewData(GetResponseForControllerResultEvent $event)
    {
    $arrayData = $event->getControllerResult();
    $event->setResponse(new JsonResponse($arrayData));
    }
    }
    14

    View Slide

  15. Denis Brumann // [email protected] // @dbrumann
    Request Response
    kernel.

    request
    kernel.

    response
    kernel.

    controller
    kernel.

    exception
    resolve
    controller
    call
    controller
    kernel.

    terminate
    Exception
    response?
    resolve
    arguments
    Symfony Event Cycle
    kernel.

    view
    15

    View Slide

  16. Denis Brumann // [email protected] // @dbrumann
    class JsonExceptionListener implements EventSubscriberInterface
    {
    public static function getSubscribedEvents(): array
    {
    return [
    KernelEvents::EXCEPTION => 'handleExceptionAsJson',
    ];
    }
    public function handleExceptionAsJson(GetResponseForExceptionEvent $event): void
    {
    ...
    }
    }
    16

    View Slide

  17. Denis Brumann // [email protected] // @dbrumann
    public function handleExceptionAsJson(GetResponseForExceptionEvent $event): void
    {
    $exception = $event->getException();
    $data = [
    'type' => get_class($exception),
    'description' => $exception->getMessage(),
    ];
    $event->setResponse(

    new JsonResponse($data, $exception->getCode())

    );
    }
    17

    View Slide

  18. Denis Brumann // [email protected] // @dbrumann
    18

    View Slide

  19. Denis Brumann // [email protected] // @dbrumann
    Request Response
    kernel.

    request
    kernel.

    response
    kernel.

    controller
    kernel.

    exception
    resolve
    controller
    call
    controller
    kernel.

    terminate
    Exception
    response?
    resolve
    arguments
    Symfony Event Cycle
    kernel.

    view
    19

    View Slide

  20. Denis Brumann // [email protected] // @dbrumann
    namespace App\Controller;
    use App\Entity\Product;
    class ProductController
    {
    public function createProduct(Product $product)
    {
    // Save product info to database
    }
    }
    20

    View Slide

  21. Denis Brumann // [email protected] // @dbrumann
    interface ArgumentValueResolverInterface
    {
    public function supports(Request $request, ArgumentMetadata $argument) : bool;
    public function resolve(Request $request, ArgumentMetadata $argument) : Generator;
    }
    21

    View Slide

  22. Denis Brumann // [email protected] // @dbrumann
    class JsonPayloadResolver implements ArgumentValueResolverInterface
    {
    private $serializer;
    private $validator;
    public function __construct(

    SerializerInterface $serializer,
    ValidatorInterface $validator

    ) {
    ...
    }
    public function supports(Request $request, ArgumentMetadata $argument)
    {
    return $request->getMethod() !== 'GET';
    }
    public function resolve(Request $request, ArgumentMetadata $argument)
    {
    ...
    }
    }
    22

    View Slide

  23. Denis Brumann // [email protected] // @dbrumann
    public function resolve(Request $request, ArgumentMetadata $argument)
    {
    $data = $this->serializer->deserialize(
    $request->getContent(),
    $argument->getType(),
    'json'
    );
    $errors = $this->validator->validate($data);
    if (count($errors)) {
    throw new BadRequestHttpException((string) $errors);
    }
    yield $data;
    }
    23
    getType(): Product::class
    getName(): "product"

    View Slide

  24. Denis Brumann // [email protected] // @dbrumann
    24

    View Slide

  25. Denis Brumann // [email protected] // @dbrumann
    Request Response
    kernel.

    request
    kernel.

    response
    kernel.

    controller
    kernel.

    exception
    resolve
    controller
    call
    controller
    kernel.

    terminate
    Exception
    response?
    resolve
    arguments
    Symfony Event Cycle
    kernel.

    view
    25
    Beispiel:

    HATEOAS

    Links hinzufügen

    View Slide

  26. Denis Brumann // [email protected] // @dbrumann
    class HypermediaResponseListener implements EventSubscriberInterface
    {
    public static function getSubscribedEvents()
    {
    return [KernelEvents::RESPONSE => 'addLinksToResponse'];
    }
    public function addLinksToResponse(FilterResponseEvent $event)
    {
    $request = $event->getRequest();
    $response = $event->getResponse();
    $content = json_decode($response->getContent(), true);
    $updated = [
    'content' => $content,
    '_links' => $this->getLinksForRequest($request, $response, $content),
    ];
    $response->setContent(json_encode($updated));
    }
    private function getLinksForRequest($request, $response, $content): array
    {
    // ...
    }
    }
    26

    View Slide

  27. Denis Brumann // [email protected] // @dbrumann
    Unsere Mini-API für Prototyping
    Eingehende Daten JSON-Input wird automatisch umgewandelt
    Ausgehende Daten Sämtlicher Output wird automatisch als JSON kodiert
    HATEOAS / HAL Response-Listener fügt automatisch Links hinzu
    27

    View Slide

  28. Denis Brumann // [email protected] // @dbrumann
    Fragen?
    28

    View Slide