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.

6a1345d8e6dd15b2c78eff0c331963b1?s=128

Denis Brumann

May 04, 2018
Tweet

Transcript

  1. 3.

    Denis Brumann // denis.brumann@sensiolabs.de // @dbrumann import React, { Component

    } from 'react'; class Checkout extends Component { constructor(props) { super(props); } } export default Checkout; 3
  2. 4.

    Denis Brumann // denis.brumann@sensiolabs.de // @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
  3. 5.

    Denis Brumann // denis.brumann@sensiolabs.de // @dbrumann import React, { Component

    } from 'react'; class Checkout extends Component { constructor(props) {...} onCheckoutConfirm() {...} render() { const {basket, shippingInfo, paymentInfo} = this.state; return ( <div className="checkout-confirmation"> ... <Confirmation onSubmit={this.onCheckoutConfirm} /> </div> ); } } export default Checkout; 5
  4. 6.

    Denis Brumann // denis.brumann@sensiolabs.de // @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
  5. 7.

    Denis Brumann // denis.brumann@sensiolabs.de // @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
  6. 12.

    Denis Brumann // denis.brumann@sensiolabs.de // @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 !❗
  7. 13.

    Denis Brumann // denis.brumann@sensiolabs.de // @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
  8. 14.

    Denis Brumann // denis.brumann@sensiolabs.de // @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
  9. 15.

    Denis Brumann // denis.brumann@sensiolabs.de // @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
  10. 16.

    Denis Brumann // denis.brumann@sensiolabs.de // @dbrumann class JsonExceptionListener implements EventSubscriberInterface

    { public static function getSubscribedEvents(): array { return [ KernelEvents::EXCEPTION => 'handleExceptionAsJson', ]; } public function handleExceptionAsJson(GetResponseForExceptionEvent $event): void { ... } } 16
  11. 17.

    Denis Brumann // denis.brumann@sensiolabs.de // @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
  12. 19.

    Denis Brumann // denis.brumann@sensiolabs.de // @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
  13. 20.

    Denis Brumann // denis.brumann@sensiolabs.de // @dbrumann <?php namespace App\Controller; use

    App\Entity\Product; class ProductController { public function createProduct(Product $product) { // Save product info to database } } 20
  14. 21.

    Denis Brumann // denis.brumann@sensiolabs.de // @dbrumann interface ArgumentValueResolverInterface { public

    function supports(Request $request, ArgumentMetadata $argument) : bool; public function resolve(Request $request, ArgumentMetadata $argument) : Generator; } 21
  15. 22.

    Denis Brumann // denis.brumann@sensiolabs.de // @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
  16. 23.

    Denis Brumann // denis.brumann@sensiolabs.de // @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"
  17. 25.

    Denis Brumann // denis.brumann@sensiolabs.de // @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
  18. 26.

    Denis Brumann // denis.brumann@sensiolabs.de // @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
  19. 27.

    Denis Brumann // denis.brumann@sensiolabs.de // @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