REST easy with API platform! #DevConMU

REST easy with API platform! #DevConMU

The web has changed! Users spend more time on mobile than on desktops and they expect to have an amazing user experience on both platforms. APIs are the heart of the new web as the central point of access data, encapsulating logic and providing the same data and same features for desktops and mobiles.

In this talk, I will show you how in only 45 minutes we can create full REST API, with documentation and admin application build with React.

29db221e8a59b06c9180725ec8ac1e75?s=128

Antonio Peric-Mazar

April 12, 2019
Tweet

Transcript

  1. REST easy with API Platform Antonio Perić-Mažar, Locastic 12.04.2019., #DevConMu

  2. None
  3. Antonio Perić-Mažar CEO @ Locastic Co-Founder @ Blockada antonio@locastic.com @antonioperic

  4. Locastic • We help clients create amazing web and mobile

    apps (since 2011) • mobile development • web development • UX/UI • Training and Consulting • Shift Conference, Symfony Croatia • www.locastic.com t: @locastic
  5. None
  6. Why API?

  7. The web has changed • Javascript web apps are standard

    (SPA) • Users spend more time on using mobile devices than desktop or TV. • Linked Data and the semantic web are a reality
  8. None
  9. APIs are the heart of this new web • Central

    point to access data (R/W data) • Encapsulate business logic • Same data and same features for desktops, mobiles, TVs and etc • It is stateless (PHP Sessions make horizontal scaling harder)
  10. Client Apps • HTML5 (SPA), mobile apps, TVs, Cars etc.

    • Holds all the presentation logic • Is downloaded first (SPA, shell model) • Queries the API to retrieve and modify data using asynchronous requests • Is 100% composed of HTML, JavaScript and assets (CSS and etc) • Can be hosted on a CDN
  11. None
  12. Immediate benefits • Speed (even on mobile) • Scalability and

    robustness • Development comfort • Long term benefits
  13. Formats, standards, patterns

  14. HTTP + REST + JSON • Work everywhere • Lightweight

    • Stateless • HTTP has a powerful caching model • Extensible (JSON-LD, Hydra, Swagger, HAL…) • High quality tooling
  15. HATEOAS / Linked Data • Hypermedia as the Engine of

    Application State • Hypermedia: IRI as identifier • Ability to reference external data (like hypertext links) • Auto discoverable <=> Generic clients
  16. None
  17. JSON-LD (JSON for Linked Data) • Standard: W3C recommandation (since

    2014) • Machine readable data • Easy to use: looks like a typical JSON document • Already used by Gmail, GitHub, BBC, Microsoft, US gov… • Compliant with technologies of the semantic web: RDF, SPARQL, triple store… • Good for SEO
  18. Hydra • Describe REST APIs in JSON-LD • = write

    support • = auto-discoverable APIs • = standard for collections, paginations, errors, filters • Draft W3C (Work In Progress)
  19. { "@context": "/contexts/Book", "@id": "/books/2", "@type": "http://schema.org/Book", "id": 2, "isbn":

    "9790070863971", "description": "A long but very interesting story about REST and asyncio.", "author": "The life!", "title": "X", "publicationDate": "2002-01-29T00:00:00+00:00" }
  20. { "@context": "contexts/Errors", "@type": “hydra:Error”, “hydra:title”: “An error occurred”, “hydra:description”:

    “Not found” }
  21. { "@context": "/contexts/Book", "@id": "/books", "@type": "hydra:Collection", "hydra:member": [ {

    "@id": "/books/2", "@type": "http://schema.org/Book", "id": 2, "isbn": "9790070863971", "description": "A long but very interesting story about REST and asyncio.", "author": "The life!", "title": "X", "publicationDate": "2002-01-29T00:00:00+00:00" }, … { "@id": "/books/31", "@type": "http://schema.org/Book", "id": 31, "isbn": "9791943452827", "description": "Tempora voluptas ut dolorem voluptates. Provident natus ipsam fugiat est ipsam quia. Sint mollitia sed facere qui sit. Ad iusto molestias iusto autem laboriosam nulla earum eius.", "author": "Miss Gladyce Nader I", "title": "Voluptas doloremque esse dolor qui illo placeat harum voluptatem.", "publicationDate": "1970-10-11T00:00:00+00:00" } ], "hydra:totalItems": 125, "hydra:view": { "@id": "/books?page=1", "@type": "hydra:PartialCollectionView", "hydra:first": "/books?page=1", "hydra:last": "/books?page=5", "hydra:next": "/books?page=2" } }
  22. “API Platform is the most advanced API platform, in any

    framework or language.” Fabien Potencier, SymfonyCon 2017
  23. None
  24. API Platform: the promise • Fully featured API supporting Swagger

    + JSON-LD + Hydra + HAL in minutes • An auto generated doc • Convenient API spec and test tools using Behat • Easy authentication management with JWT or OAuth • CORS and HTTP cache • All the tools you love: Doctrine ORM, Monolog, Swiftmailer...
  25. API Platform <3 Symfony • Built on top of Symfony

    full-stack • Install any existing SF bundles • Follow SF Best Practices • Use your Symfony skills • Can be used in your existing SF app • (Optional) tightly integrated with Doctrine
  26. Features • CRUD • Filters • Serializations groups and relations

    • Validation • Pagination • Sorting • The event system • Content Negation • Extensions • HTTP and reverse proxy caching • Invalidation-based HTTP caching • JS Admin apps • GraphQL support • And basically everything needed to build modern APIs
  27. How to start? • Download distribution and use Docker (includes

    frontend applications, etc) • https://github.com/api-platform/api-platform/releases/tag/v2.4.2 • Use Symfony Flex and any setup you want (little harder to setup) • Enjoy!!!
  28. CRUD <?php namespace App\Entity; class Author { private $id; private

    $firstName; private $lastName; // ... } # api/config/packages/api_platform/ author.yaml resources: App\Entity\Author:~
  29. None
  30. schema.org

  31. None
  32. None
  33. None
  34. { "@context": "http://schema.org", "@type": "FlightReservation", "reservationNumber": "RXJ34P", "reservationStatus": "http://schema.org/Confirmed", "underName":

    { "@type": "Person", "name": "Eva Green" }, "reservationFor": { "@type": "Flight", "flightNumber": "110", "airline": { "@type": "Airline", "name": "United", "iataCode": "UA" }, "departureAirport": { "@type": "Airport", "name": "San Francisco Airport", "iataCode": "SFO" }, "departureTime": "2017-03-04T20:15:00-08:00", "arrivalAirport": { "@type": "Airport", "name": "John F. Kennedy International Airport", "iataCode": "JFK" }, "arrivalTime": "2017-03-05T06:30:00-05:00" } }
  35. Using schema.org in Api Platform resources: App\Entity\FlightReservation: iri: 'http://schema.org/FlightReservation'

  36. Using schema.org in Api Platform resources: App\Entity\FlightReservation: iri: 'http://schema.org/FlightReservation' properties:

    status: iri: 'http://schema.org/reservationStatus'
  37. Operations • API Platform Core relies on the concept of

    operations. Operations can be applied to a resource exposed by the API. From an implementation point of view, an operation is a link between a resource, a route and its related controller. • There are two types of operations: • Collection operations act on a collection of resources. By default two routes are implemented: POST and GET. • Item operations act on an individual resource. 3 default routes are defined GET, PUT and DELETE.
  38. None
  39. Custom operation class Match { private $id; /** * @Groups({"match_read"})

    */ private $datetime; /** * @Groups({"match_read", "match_write"}) */ private $playerOnePoints; /** * @Groups({"match_read", "match_write"}) */ private $playerTwoPoints; /** * @Groups({"match_read", "match_write"}) */ private $playerOne; /** * @Groups({"match_read", "match_write"}) */ private $playerTwo; /** * @Groups({"match_read"}) */ private $winner; /** * @Groups({"match_read"}) */ private $result; }
  40. Custom operation class Match { private $id; /** * @Groups({"match_read"})

    */ private $datetime; /** * @Groups({"match_read", "match_write"}) */ private $playerOnePoints; /** * @Groups({"match_read", "match_write"}) */ private $playerTwoPoints; /** * @Groups({"match_read", "match_write"}) */ private $playerOne; /** * @Groups({"match_read", "match_write"}) */ private $playerTwo; /** * @Groups({"match_read"}) */ private $winner; /** * @Groups({"match_read"}) */ private $result; } class MatchController extends Controller { /** * @param Match $data * * @return Match */ public function getMatchAction($data) { $result = $data->getPlayerOne() . ' ' . $data->getPlayerOnePoints().':' .$data->getPlayerTwoPoints() . ' ' . $data->getPlayerTwo(); $data->setResult($result); return $data; } }
  41. Custom operation class Match { private $id; /** * @Groups({"match_read"})

    */ private $datetime; /** * @Groups({"match_read", "match_write"}) */ private $playerOnePoints; /** * @Groups({"match_read", "match_write"}) */ private $playerTwoPoints; /** * @Groups({"match_read", "match_write"}) */ private $playerOne; /** * @Groups({"match_read", "match_write"}) */ private $playerTwo; /** * @Groups({"match_read"}) */ private $winner; /** * @Groups({"match_read"}) */ private $result; } class MatchController extends Controller { /** * @param Match $data * * @return Match */ public function getMatchAction($data) { $result = $data->getPlayerOne() . ' ' . $data->getPlayerOnePoints().':' .$data->getPlayerTwoPoints() . ' ' . $data->getPlayerTwo(); $data->setResult($result); return $data; } } # app/config/routing.yml get_match: path: /api/v1/matches/{id}.{_format} methods: ['GET'] defaults: _controller: AppBundle:Match:getMatch _api_resource_class: AppBundle\Entity\Match _api_item_operation_name: get
  42. Override default order resources: App\Entity\Player: attributes: order: lastname: ‘ASC’

  43. Adding subresource resources: AppBundle\Entity\Player: properties: matches: subresource: resourceClass: ‘App\Entity\Match’ maxDepth:

    1 collection: true -> api.com/players/1/matches
  44. Filters • If Doctrine ORM support is enabled, adding filters

    is as easy as registering a filter service in your api/config/services.yml file and adding an attribute to your resource configuration. • Filters add extra conditions to base database query • Useful filters for the Doctrine ORM are provided with the library. You can also create custom filters that would fit your specific needs.
  45. Filters • Search filter (partial, start, end, exact, ipartial, iexact)

    • Date filter (?property[<after|before>]=value ) • Boolean filter (?property=[true|false|1|0]) • Numeric filter (?property=int|bigint|decimal) • Range filter (?property[lt]|[gt]|[lte]|[gte]|[between]=value) • Order filter (?order[property]=<asc|desc>) • … • Custom filters
  46. Filters examples # AppBundle/Resources/config/api_resources/resources.yml resources: AppBundle\Entity\Player: # ... attributes: filters:

    ['player.search', 'player.order'] AppBundle\Entity\Match: # ... attributes: filters: ['match.date'] services: player.search_filter: parent: 'api_platform.doctrine.orm.search_filter' arguments: [ { id: 'exact', email: 'exact', firstName: 'partial' } ] tags: [ { name: 'api_platform.filter', id: 'player.search' } ] match.date_filter: parent: 'api_platform.doctrine.orm.date_filter' arguments: [ { datetime: ~ } ] tags: [ { name: 'api_platform.filter', id: 'match.date' } ] player.order_filter: parent: 'api_platform.doctrine.orm.order_filter' arguments: [{ firstName: 'ASC', lastName: 'ACS', email: ~ }] tags: [{ name: 'api_platform.filter', id: 'player.order' }]
  47. Serialization Groups • API Platform Core allows to choose which

    attributes of the resource are exposed during the normalization (read) and denormalization (write) process. It relies on the serialization (and deserialization) groups feature of the Symfony Serializer component. • allows to specify the definition of serialization using XML, YAML, or annotations.
  48. Serialization Groups

  49. <?php namespace App\Entity; use FOS\UserBundle\Model\User as BaseUser; use Symfony\Component\Serializer\Annotation\Groups; class

    Player extends BaseUser { /** * @var int */ protected $id; /** * @Groups({"player_read", "player_write"}) */ private $firstName; /** * @Groups({"player_read", "player_write"}) */ private $lastName; /** * @Groups({"player_read", "player_write"}) */ protected $email; // ... } # UserBundle/Resources/api_resources/resources.yml resources: App\Entity\Player: attributes: normalization_context: groups: ['player_read'] denormalization_context: groups: ['player_write']
  50. Using Different Serialization Groups per Operation # UserBundle/Resources/api_resources/resources.yml resources: App\Entity\Player:

    itemOperations: get: method: 'GET' normalization_context: groups: ['player_read', 'player_extra'] put: method: 'PUT' delete: method: 'DELETE' attributes: normalization_context: groups: ['player_read'] denormalization_context: groups: ['player_write']
  51. Api platform events

  52. // src/AppBundle/EventSubscriber/MatchEventSubscriber.php class MatchEventSubscriber implements EventSubscriberInterface { private $matchHelper; public

    function __construct(MatchHelper $matchHelper) { $this->matchHelper = $matchHelper; } public static function getSubscribedEvents() { return [ KernelEvents::VIEW => [['addWinner', EventPriorities::POST_VALIDATE]], ]; } public function addWinner(GetResponseForControllerResultEvent $event) { $match = $event->getControllerResult(); $method = $event->getRequest()->getMethod(); if(!$match instanceof Match || $method !== 'POST') { return; } $winner = $this->matchHelper->getWinner($match); $match->setWinner($winner); } }
  53. Extensions • API Platform Core provides a system to extend

    queries on items and collections. • Custom extensions must implement the ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\Query CollectionExtensionInterface and / or the ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\Query ItemExtensionInterface interfaces, to be run when querying for a collection of items and when querying for an item respectively.
  54. class GetPlayersExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface { public function applyToItem( QueryBuilder

    $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, string $operationName = null, array $context = [] ) { $this->addWhere($queryBuilder, $resourceClass, $operationName); } public function applyToCollection( QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null ) { $this->addWhere($queryBuilder, $resourceClass, $operationName); } private function addWhere(QueryBuilder $queryBuilder, string $resourceClass, string $operationName = null) { if ($resourceClass != Player::class || $operationName != 'get') { return; } $rootAlias = $queryBuilder->getRootAliases()[0]; $queryBuilder->andWhere( $queryBuilder->expr()->eq($rootAlias.'.enabled', ':enabled') )->setParameter('enabled', true); } }
  55. services: app.extension.get_players: class: AppBundle\Doctrine\ORM\Extension\GetPlayersExtension public: false tags: - { name:

    api_platform.doctrine.orm.query_extension.collection, priority: 9 } - { name: api_platform.doctrine.orm.query_extension.item }
  56. Per Resource Authorization Mechanism namespace AppBundle\Entity; use ApiPlatform\Core\Annotation\ApiResource; use Doctrine\ORM\Mapping

    as ORM; /** * @ApiResource( * attributes={"is_granted"="has_role('ROLE_ADMIN')"}, * itemOperations={ * "get"={"method"="GET", "is_granted"="object.getOwner() == user"} * } * ) * @ORM\Entity */ class Secured { /** * @ORM\Column(type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ public $id; /** * @ORM\Column(type="text") */ public $owner;
  57. Cache invalidation is builtin

  58. Translation Support • https://github.com/Locastic/ApiPlatformTranslationBundle • https://locastic.com/blog/having-troubles-with-implementing-translations-in- apiplatform/

  59. GraphQL support

  60. GraphQL Support • docker-compose exec php composer req webonyx/graphql-php &&

    docker- compose exec php bin/console cache:clear • The GraphQL implementation supports queries, mutations, 100% of the Relay server specification, pagination, filters and access control rules. You can use it with the popular RelayJS and Apollo clients.
  61. None
  62. Testing • PHPUnit • Postman (newman) • Panther • https://locastic.com/blog/what-to-do-when-you-get-lost-in-api-testing/

  63. Mercure

  64. Mercure • Fast, written in Go • native browser support,

    no lib nor SDK required (built on top of HTTP and server-sent events) • compatible with all existing servers, even those who don't support persistent connections (serverless architecture, PHP, FastCGI...) • Automatic HTTP/2 and HTTPS (using Let's Encrypt) support • CORS support, CSRF protection mechanism • Cloud Native, follows the Twelve-Factor App methodology • Open source (AGPL) • …
  65. Let’s try it https://symfonycon.les-tilleuls.coop/

  66. Update progressive web app • docker-compose exec client generate-api-platform-client •

    Follow instructions
  67. Version 2.4 • read and write support for MongoDB, the

    reference document database, including a lot of useful filters • Read support for Elasticsearch, the open source search and analytics engine, including filters for advanced search • Automatic “push” of updated resources from the server to the clients using the brand new Mercure protocol
  68. Version 2.4 • Integration with the Symfony Messenger component to

    easily implement the CQRS pattern and to handle messages asynchronously (using brokers such as RabbitMQ, Apache Kafka, Amazon SQS or Google PubSub) • Ability to leverage the “Server Push” feature of HTTP/2 to preemptively send the relations of a requested resource to the client • Automatic availability of list filters in the React-based admin when a corresponding one is available API-side • Full compatibility with the version 3 of the OpenAPI specification format (formerly known as Swagger), and integration of the beautiful ReDoc documentation generator
  69. Version 2.4 • Improved DTOs support • Per resource configuration

    of HTTP cache headers • Ability to easily use the Sunset HTTP header to advertise the removal date of deprecated endpoints
  70. None
  71. GFNY Ticketing system • 20 races, 18 countries • Different

    timezones, currencies, languages… • More then 60 000 tickets per year, with very complex login • Standings and rankings • Email notification, flexibility • Etc.
  72. Conclusion • Very powerful, even for large applications • Easy

    to start, well documented • Active community and company behind it • Write tests!
  73. Thank you!

  74. antonio@locastic.com @antonioperic www.locastic.com Questions?