Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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 import React, { Component

    } from 'react'; class Checkout extends Component { constructor(props) { super(props); } } export default Checkout; 3
  2. 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
  3. 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 ( <div className="checkout-confirmation"> ... <Confirmation onSubmit={this.onCheckoutConfirm} /> </div> ); } } export default Checkout; 5
  4. 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
  5. 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
  6. 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 !❗
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. Denis Brumann // [email protected] // @dbrumann <?php namespace App\Controller; use

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

    function supports(Request $request, ArgumentMetadata $argument) : bool; public function resolve(Request $request, ArgumentMetadata $argument) : Generator; } 21
  15. 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
  16. 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"
  17. 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
  18. 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
  19. 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