O slideshow foi denunciado.
Utilizamos seu perfil e dados de atividades no LinkedIn para personalizar e exibir anúncios mais relevantes. Altere suas preferências de anúncios quando desejar.

PHP, the GraphQL ecosystem and GraphQLite

Creating a GraphQL API is more and more common for PHP developers. The task can seem complex but there are a lot of tools to help.

In this talk given at AFUP Paris PHP Meetup, I'm presenting GraphQL and why it is important. Then, I'm having a look at the existing libraries in PHP. Finally, I'm diving in the details of GraphQLite; a library that creates a GraphQL schema by analyzing your PHP code.

  • Entre para ver os comentários

  • Seja a primeira pessoa a gostar disto

PHP, the GraphQL ecosystem and GraphQLite

  1. 1. GraphQL in the PHP ecosystem
  2. 2. What is GraphQL?
  3. 3. GraphQL ? • GraphQL is a protocol
  4. 4. GraphQL ? • GraphQL is a protocol • It is not: • A fancy new database • A database query language like SQL
  5. 5. GraphQL ? • GraphQL is a protocol • GraphQL is a challenger to those other protocols: • REST • Web-services (SOAP/WSDL based)
  6. 6. A bit of history Web-services (~1999) • Strongly typed • Self-describing (WSDL) • XML-based
  7. 7. A bit of history Web-services (~1999) • Strongly typed • Self-describing (WSDL) • XML-based
  8. 8. A bit of history Web-services (~1999) REST (~2005) • Strongly typed • Self-describing (WSDL) • XML-based • Weakly typed • Non self-describing (still OpenAPI for doc) • Mostly JSON based
  9. 9. A bit of history Web-services (~1999) REST (~2005) • Strongly typed • Self-describing (WSDL) • XML-based • Weakly typed • Non self-describing (still OpenAPI for doc) • Mostly JSON based
  10. 10. A bit of history Web-services (~1999) REST (~2005) GraphQL (2015) • Strongly typed • Self-describing (WSDL) • XML-based • Weakly typed • Non self-describing (still OpenAPI for doc) • Mostly JSON based • Strongly typed • Self-describing • JSON based • + Client driven queries
  11. 11. GraphQL ? It is developed by Facebook and was first used in the Facebook API. Facebook also provides: • A JS client (to query a GraphQL server) • A NodeJS server library Tons of server implementations exist.
  12. 12. Why GraphQL?
  13. 13. What problem does GraphQL solves? • Your API often changes • You develop a new feature but your API does not exactly respond to your needs.
  14. 14. What problem does GraphQL solves? • For instance: you are developing a marketplace. You need a page to display a product, along some company information. /api/product/42 /api/company/35 REST
  15. 15. What problem does GraphQL solves? • Alternative, still REST • But what if some pages don’t need the company details? /api/product/42 REST
  16. 16. What problem does GraphQL solves? • Yet another alternative, still REST /api/product/42?with_company=true REST Flags hell 😨! Probably one flag per consumer of the API
  17. 17. What problem does GraphQL solves? • GraphQL to the rescue! • GraphQL is a paradigm shift. • The client asks for the list of fields it wants. GET /graphql?query= Single endpoint The name of the query is « products » List of fields requested
  18. 18. What problem does GraphQL solves? • GraphQL to the rescue! • Another request of the same query with a different set of fields No need to change the code on the server-side! All this data in one API call! GET /graphql?query=
  19. 19. GraphQL types
  20. 20. Types GraphQL is strongly typed. It comes with a « schema language » but this is rarely used while developing. It is however useful to understand what is going on.
  21. 21. ≠ Schema language product(id: 42) { name company { name logo country { name } } } Query language
  22. 22. Types Note: • [Product] ➔ an array of Products • String ➔ a string (or null) • String! ➔ a non-nullable string Hence: • [Product!]! ➔ An array (non- nullable) of products that are also non-nullable.
  23. 23. Types Some « scalar » types: • ID: a unique identifier (~=String) • String • Int • Float • Boolean No support for « Date » in the standard (but custom types are supported by some implementations)
  24. 24. Types Support for “arguments”: • product(id: ID!) ➔ the product query requires an “id” field of type “ID” to be passed.
  25. 25. Types Bonus: • Support for interfaces • Support for Union types • Support for “InputType” (to pass complex objects in queries)
  26. 26. Mutations So far, we mostly talked about queries (because this is what is fun in GraphQL). GraphQL can also do mutations (to change the state of the DB)
  27. 27. Out of GraphQL scope
  28. 28. Transport is out of scope • You usually do GraphQL over HTTP/HTTPS • But nothing prevents you from using GraphQL over UDP, or mail, …
  29. 29. Transport is out of scope • You usually do GraphQL over HTTP/HTTPS • But nothing prevents you from using GraphQL over UDP, or mail, or homing pigeon whatever!
  30. 30. Authentication is out of scope • GraphQL does not deal with authentication. • You are free to deal with it in any way you want: • Session-based • or using Oauth2 • or using a token… • Authentication can happen using a REST API, or even using a GraphQL mutation!
  31. 31. Pagination is out of scope • GraphQL does not deal with pagination in the standard. • You are free to add limit/offset parameters to your queries • “Relay” is providing some conventions to deal with pagination (more on that later)
  32. 32. The GraphQL ecosystem
  33. 33. Ecosystem (a small part of…) Browser GraphQL Client Server GraphQL Middleware DB RelayJS Apollo ReactJS Express- graphql NodeJS Webonyx/ GraphQL- PHP PHP GraphiQL (for dev!)
  34. 34. Zoom on GraphQL in PHP Core library Wrapper library • Low level • Parsing • Service requests • Powerful • Feature complete • Hard to use (poor DX) • High level • Opiniated • Easy to use
  35. 35. Zoom on GraphQL in PHP Core library Wrapper library • webonyx/graphql-php • De-facto standard in PHP • Youshido/GraphQL •  Abandonned 
  36. 36. Zoom on GraphQL in PHP Core library Wrapper library • API Platform (Symfony) • Overblog GraphQL Bundle (Symfony) • Lighthouse (Laravel) • … and now GraphQLite
  37. 37. Zoom of Webonyx/GraphQL-PHP Define a type
  38. 38. Zoom of Webonyx/GraphQL-PHP Define a query
  39. 39. Zoom of Webonyx/GraphQL-PHP Actually resolving a query
  40. 40. Costs VS benefits Costs Gains Strict types Self-described Client driven Work
  41. 41. You need a wrapper library Costs Gains Strict types Self-described Client driven Work GraphQL library
  42. 42. Strategies Schema-first Code-first • Design the GraphQL schema first • Find a way to link it to your code • Design your domain code • Generate the schema from the code
  43. 43. Strategies Schema-first Code-first • Overblog GraphQL Bundle • Lighthouse • API Platform • GraphQLite
  44. 44. Strategies Schema-first Code-first • Overblog GraphQL Bundle • Lighthouse • API Platform • GraphQLite Lighthouse
  45. 45. Strategies Schema-first Code-first • Overblog GraphQL Bundle • Lighthouse • API Platform • GraphQLite API Platform
  46. 46. GraphQLite
  47. 47. The idea Let’s imagine we want to do a simple “echo” query in PHP.
  48. 48. The idea Let’s imagine we want to do a simple “echo” query in PHP.
  49. 49. The idea Using webonyx/GraphQL-PHP type Query { echo(message: String!): String }
  50. 50. The idea The same “echo” method in pure PHP function echoMsg(string $message): string { return $message; }
  51. 51. function echoMsg(string $message): string { return $message; } The idea The same “echo” method in pure PHP Query name Arguments Return type Resolver
  52. 52. The idea The same “echo” method in pure PHP /** * @Query */ function echoMsg(string $message): string { return $message; }
  53. 53. The idea • PHP is already typed. • We should be able to get types from PHP and convert them to a GraphQL schema PHP objects GraphQL objects GraphQLite
  54. 54. Works well with Doctrine Bonus: • It plays nice with Doctrine ORM too • (we also have native bindings with TDBM, our in-house ORM) DB model PHP objects GraphQL objects Doctrine GraphQLite
  55. 55. GraphQLite GraphQLite is: • Framework agnostic • Symfony bundle and Laravel package available • PHP 7.2+ • Based on Webonyx/GraphQL-PHP
  56. 56. Demo time!
  57. 57. Our playground
  58. 58. First query namespace AppController; use AppEntityPost; use AppRepositoryPostRepository; use TheCodingMachineGraphQLiteAnnotationsQuery; class PostController { /** * @var PostRepository */ private $postRepository; public function __construct(PostRepository $postRepository) { $this->postRepository = $postRepository; } /** * @Query() */ public function getPosts(?string $search): array { return $this->postRepository->findAllFilterBySearch($search); } }
  59. 59. First query
  60. 60. First query namespace AppController; use AppEntityPost; use AppRepositoryPostRepository; use TheCodingMachineGraphQLiteAnnotationsQuery; class PostController { /** * @var PostRepository */ private $postRepository; public function __construct(PostRepository $postRepository) { $this->postRepository = $postRepository; } /** * @Query() * @return Post[] */ public function getPosts(?string $search): array { return $this->postRepository->findAllFilterBySearch($search); } }
  61. 61. First query
  62. 62. <?php namespace AppEntity; use TheCodingMachineGraphQLiteAnnotationsField; use TheCodingMachineGraphQLiteAnnotationsType; /** * @Type() */ class Post { //… /** * @Field(outputType="ID") */ public function getId(): int { return $this->id; } /** * @Field() */ public function getMessage(): string { return $this->message; } /** * @Field() */ public function getCreated(): DateTimeImmutable { return $this->created; } }
  63. 63. First query
  64. 64. First query <?php namespace AppEntity; use TheCodingMachineGraphQLiteAnnotationsField; use TheCodingMachineGraphQLiteAnnotationsType; /** * @Type() */ class Post { // … /** * @Field() */ public function getAuthor(): User { return $this->author; } }
  65. 65. First query
  66. 66. First query <?php namespace AppEntity; use TheCodingMachineGraphQLiteAnnotationsField; use TheCodingMachineGraphQLiteAnnotationsType; /** * @Type() */ class User implements UserInterface { // … /** * @Field(outputType="ID!") */ public function getId(): int { return $this->id; } /** * @Field() */ public function getLogin(): string { return $this->login; } // …
  67. 67. First query
  68. 68. Improving our sample
  69. 69. Improving our sample What if I want to get the list of comments from my “post” type? (Assuming I have no “getComments” method in the Post class)
  70. 70. Improving our sample Say hello to “type extension”! You can add fields in an existing type, using the “@ExtendType” annotation.
  71. 71. Improving our sample namespace AppTypes; use AppEntityComment; use AppRepositoryCommentRepository; use TheCodingMachineGraphQLiteAnnotationsExtendType; use AppEntityPost; use TheCodingMachineGraphQLiteAnnotationsField; /** * @ExtendType(class=Post::class) */ class PostType { /** * @var CommentRepository */ private $commentRepository; public function __construct(CommentRepository $commentRepository) { $this->commentRepository = $commentRepository; } /** * @Field() * @return Comment[] */ public function getComments(Post $post): array { return $this->commentRepository->findByPost($post); } }
  72. 72. Improving our sample namespace AppTypes; use AppEntityComment; use AppRepositoryCommentRepository; use TheCodingMachineGraphQLiteAnnotationsExtendType; use AppEntityPost; use TheCodingMachineGraphQLiteAnnotationsField; /** * @ExtendType(class=Post::class) */ class PostType { /** * @var CommentRepository */ private $commentRepository; public function __construct(CommentRepository $commentRepository) { $this->commentRepository = $commentRepository; } /** * @Field() * @return Comment[] */ public function getComments(Post $post): array { return $this->commentRepository->findByPost($post); } } It is a service!
  73. 73. Improving our samplenamespace AppTypes; use AppEntityComment; use AppRepositoryCommentRepository; use TheCodingMachineGraphQLiteAnnotationsExtendType; use AppEntityPost; use TheCodingMachineGraphQLiteAnnotationsField; /** * @ExtendType(class=Post::class) */ class PostType { /** * @var CommentRepository */ private $commentRepository; public function __construct(CommentRepository $commentRepository) { $this->commentRepository = $commentRepository; } /** * @Field() * @return Comment[] */ public function getComments(Post $post): array { return $this->commentRepository->findByPost($post); } } Current object is passed as first parameter
  74. 74. Improving our sample
  75. 75. Improving our sample Even better: fields can have their own arguments. For instance, you may want to fetch a paginated list of comments.
  76. 76. Improving our sample namespace AppTypes; use AppEntityComment; use AppRepositoryCommentRepository; use TheCodingMachineGraphQLiteAnnotationsExtendType; use AppEntityPost; use TheCodingMachineGraphQLiteAnnotationsField; /** * @ExtendType(class=Post::class) */ class PostType { // … /** * @Field() * @return Comment[] */ public function getComments(Post $post, int $limit = 20, int $offset = 0): array { return $this->commentRepository->findByPost($post) ->setMaxResults($limit) ->setFirstResult($offset) ->getResult(); } } From the 2nd parameter, arguments are added to the field
  77. 77. Improving our sample
  78. 78. Native pagination? Yes please!
  79. 79. Native pagination Actually, you don’t even have to bother adding pagination as GraphQLite integrates natively with Porpaginas. (Porpaginas integrates with Doctrine and TDBM)
  80. 80. Native pagination use PorpaginasDoctrineORMORMQueryResult; /** * @ExtendType(class=Post::class) */ class PostType { // … /** * @Field() * @return Comment[] */ public function getComments(Post $post): ORMQueryResult { return new ORMQueryResult($this->commentRepository->findByPost($post)); } } Needed for Doctrine ORM. Not even necessary for TDBM 5 ResultIterator’s that can be returned directly.
  81. 81. Authentication and authorization
  82. 82. Authentication and authorization @Logged and @Right annotations can be used with @Field too!
  83. 83. More features?
  84. 84. More features! TDBM integration Inheritance / interfaces
  85. 85. More features! Everything is clearly documented at: Thanks @JUN for the help https://graphqlite.thecodingmachine.io
  86. 86. What’s next? • Release of GraphQLite 4 in June • Deferred type support • Injection of services in method parameters • Improved performances • Custom scalar types • Enum’s support • …
  87. 87. So… GraphQL everywhere?
  88. 88. GraphQL everywhere? • GraphQLite makes it trivial to write a GraphQL API. It is now easier to start a GraphQL API than a REST API! o/ • GraphQL makes a lot of sense for most of our projects because it eases the separation between front-end and back-end • And the tooling in JS/TS is awesome
  89. 89. GraphQL everywhere? • Performance warning! GraphQL itself is fast but… • N+1 problem • It is easy to write slow queries ➔ Warning with front facing websites.
  90. 90. GraphQL everywhere? • Two strategies available to avoid the “N+1” problem: • Analyzing the GraphQL query and “joining” accordingly • Or the “data-loader” pattern • + a need to set limits on the queries complexity to avoid “rogue” queries
  91. 91. Questions? @david_negrier @moufmouf graphqlite.thecodingmachine.io We are hiring! More cool stuff: • https://www.thecodingmachine.com/open-source/ • https://thecodingmachine.io David Négrier
  92. 92. From the client side … Apollo to the rescue!
  93. 93. Zoom on Apollo • Apollo is a GraphQL client (it is a JS lib). • It has bindings with: • Angular • React • VueJS • You bundle a React/Angular/Vue component in a Apollo component and Apollo takes in charge the query to the server
  94. 94. Dependencies in package.json { "devDependencies": { "@symfony/webpack-encore": "^0.22.0", "axios": "^0.18.0", "vue": "^2.5.17", "vue-loader": "^15.4.2", "vue-router": "^3.0.2", "vue-template-compiler": "^2.5.17", "vuex": "^3.0.1", "webpack-notifier": "^1.6.0", "apollo-cache-inmemory": "^1.3.12", "apollo-client": "^2.4.8", "apollo-link": "^1.2.6", "apollo-link-http": "^1.5.9", "graphql": "^14.0.2", "graphql-tag": "^2.10.0", "vue-apollo": "^3.0.0-beta.27" }, "license": "UNLICENSED", "private": true, "scripts": { "dev-server": "encore dev-server", "dev": "encore dev", "watch": "encore dev --watch", "build": "encore production --progress" } }
  95. 95. Declarative usage <template> <div> <div class="row col"> <h1>Posts</h1> </div> <form> <div class="form-row"> <div class="col-8"> <input v-model="search" type="text" class="form-control" placeholder="Search"> </div> </div> </form> <ApolloQuery :query="require('../graphql/posts.gql')" :variables="{ search }" > <template slot-scope="{ result: { loading, error, data } }"> // ……… Do something with {{data}} </template> </ApolloQuery> </div> </template> <script> export default { name: 'posts', data () { return { search: '' }; }, } </script>
  96. 96. graphql/posts.gql query searchPosts ($search: String) { posts(search: $search) { id message author { login } comments { items { id message author { login } } } } }
  97. 97. Declarative usage <template> <div> <div class="row col"> <h1>Posts</h1> </div> <form> <div class="form-row"> <div class="col-8"> <input v-model="search" type="text" class="form-control" placeholder="Search"> </div> </div> </form> <ApolloQuery :query="require('../graphql/posts.gql')" :variables="{ search }" > <template slot-scope="{ result: { loading, error, data } }"> // ……… Do something with {{data}} </template> </ApolloQuery> </div> </template> <script> export default { name: 'posts', data () { return { search: '' }; }, } </script>
  98. 98. Declarative usage <ApolloQuery :query="require('../graphql/posts.gql')" :variables="{ search }" > <template slot-scope="{ result: { loading, error, data } }"> <!-- Loading --> <div v-if="loading" class="loading apollo row col">Loading...</div> <!-- Error --> <div v-else-if="error" class="error apollo row col">An error occurred</div> <!-- Result --> <div v-else-if="data" class="result apollo "> <div v-for="post in data.posts" class="border"> <div class="row"> <div class="col"> <h3>Article</h3> {{ post.message }} </div> </div> <div class="row col"><em>Authored by: {{ post.author.login }}</em></div> <div class="row"> <div class="col"> <h3>Comments</h3> <div v-for="comment in post.comments.items" class="border"> <div class="row border"> <div class="col"> <p>Comment:</p> {{ comment.message }} <p>By: {{ comment.author.login }}</p> </div> </div> </div> </div> </div> </div> </div> <!-- No result --> <div v-else class="no-result apollo">No result :(</div> </template> </ApolloQuery>
  99. 99. But where is Redux? • Apollo comes internally with its own store. • Redux is really less useful with Apollo and you can simply scrap ~90% of your reducers. • Still useful for niche places (like managing the current logged user)
  100. 100. Questions? @david_negrier @moufmouf graphqlite.thecodingmachine.io We are hiring! More cool stuff: • https://www.thecodingmachine.com/open-source/ • https://thecodingmachine.io David Négrier

×