Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

Antonio Perić-Mažar CEO @ Locastic Co-Founder @ Blockada [email protected] @antonioperic

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

Why API?

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

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)

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

Immediate benefits • Speed (even on mobile) • Scalability and robustness • Development comfort • Long term benefits

Slide 13

Slide 13 text

Formats, standards, patterns

Slide 14

Slide 14 text

HTTP + REST + JSON • Work everywhere • Lightweight • Stateless • HTTP has a powerful caching model • Extensible (JSON-LD, Hydra, Swagger, HAL…) • High quality tooling

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

Hydra • Describe REST APIs in JSON-LD • = write support • = auto-discoverable APIs • = standard for collections, paginations, errors, filters • Draft W3C (Work In Progress)

Slide 19

Slide 19 text

{ "@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" }

Slide 20

Slide 20 text

{ "@context": "contexts/Errors", "@type": “hydra:Error”, “hydra:title”: “An error occurred”, “hydra:description”: “Not found” }

Slide 21

Slide 21 text

{ "@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" } }

Slide 22

Slide 22 text

“API Platform is the most advanced API platform, in any framework or language.” Fabien Potencier, SymfonyCon 2017

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

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...

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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!!!

Slide 28

Slide 28 text

CRUD

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

schema.org

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

{ "@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" } }

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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.

Slide 38

Slide 38 text

No content

Slide 39

Slide 39 text

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; }

Slide 40

Slide 40 text

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; } }

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

Override default order resources: App\Entity\Player: attributes: order: lastname: ‘ASC’

Slide 43

Slide 43 text

Adding subresource resources: AppBundle\Entity\Player: properties: matches: subresource: resourceClass: ‘App\Entity\Match’ maxDepth: 1 collection: true -> api.com/players/1/matches

Slide 44

Slide 44 text

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.

Slide 45

Slide 45 text

Filters • Search filter (partial, start, end, exact, ipartial, iexact) • Date filter (?property[]=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]=) • … • Custom filters

Slide 46

Slide 46 text

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' }]

Slide 47

Slide 47 text

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.

Slide 48

Slide 48 text

Serialization Groups

Slide 49

Slide 49 text

Slide 50

Slide 50 text

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']

Slide 51

Slide 51 text

Api platform events

Slide 52

Slide 52 text

// 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); } }

Slide 53

Slide 53 text

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.

Slide 54

Slide 54 text

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); } }

Slide 55

Slide 55 text

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 }

Slide 56

Slide 56 text

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;

Slide 57

Slide 57 text

Cache invalidation is builtin

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

GraphQL support

Slide 60

Slide 60 text

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.

Slide 61

Slide 61 text

No content

Slide 62

Slide 62 text

Testing • PHPUnit • Postman (newman) • Panther • https://locastic.com/blog/what-to-do-when-you-get-lost-in-api-testing/

Slide 63

Slide 63 text

Mercure

Slide 64

Slide 64 text

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) • …

Slide 65

Slide 65 text

Let’s try it https://symfonycon.les-tilleuls.coop/

Slide 66

Slide 66 text

Update progressive web app • docker-compose exec client generate-api-platform-client • Follow instructions

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

No content

Slide 71

Slide 71 text

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.

Slide 72

Slide 72 text

Conclusion • Very powerful, even for large applications • Easy to start, well documented • Active community and company behind it • Write tests!

Slide 73

Slide 73 text

Thank you!

Slide 74

Slide 74 text

[email protected] @antonioperic www.locastic.com Questions?