{"id":19697145,"url":"https://github.com/janit/wsc17-slides","last_synced_at":"2025-08-12T12:33:17.096Z","repository":{"id":69358463,"uuid":"101634172","full_name":"janit/wsc17-slides","owner":"janit","description":"Slides from the Web Summer Camp 2017 session on eZ Platform, REST API and GraphQL","archived":false,"fork":false,"pushed_at":"2017-08-31T14:36:06.000Z","size":3569,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-08-02T20:04:08.734Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/janit.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-08-28T11:01:22.000Z","updated_at":"2017-08-28T11:02:18.000Z","dependencies_parsed_at":"2023-02-21T04:30:22.995Z","dependency_job_id":null,"html_url":"https://github.com/janit/wsc17-slides","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/janit/wsc17-slides","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janit%2Fwsc17-slides","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janit%2Fwsc17-slides/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janit%2Fwsc17-slides/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janit%2Fwsc17-slides/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/janit","download_url":"https://codeload.github.com/janit/wsc17-slides/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janit%2Fwsc17-slides/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270061081,"owners_count":24520238,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-12T02:00:09.011Z","response_time":80,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-11T19:37:19.716Z","updated_at":"2025-08-12T12:33:17.067Z","avatar_url":"https://github.com/janit.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Doctrine ORM with eZ Platform REST API\u003cbr /\u003eand GraphQL\n\n\u003ccenter\u003e\u003csmall\u003eJani Tarvainen, September 1st 2017\u003c/small\u003e\u003c/center\u003e\n\n---\n\n## http://janit.iki.fi/ez-rest-graphql\n\n---\n\n\n## Agenda\n\n - Doctrine ORM as a backend for eZ Platform REST\n - Using GraphQL with eZ Platform\n - Extending GraphQL with a Doctrine endpoint\n\n---\n\n## Doctrine ORM as a backend\u003cbr/\u003efor eZ Platform REST\n\n--\n\n### Use an appropriate storage backend\n\n - The eZ REST can be used for alternative backends\n - Not everything should be stored in the content repository, you can use other backends for parts of your data\n - You can use shared REST infrastructure from eZ Platform\n - Makes it familiar to work with and to consume\n\n--\n\n### Storing WSC attendees in Doctrine ORM\n\n - Using Doctrine ORM entities to store attendee information\n - Shared Database connection, separate database tables\n - eZ Platform is unaware of Doctrine and the entities\n - Exposing them are identical to eZ Content Objects\n\n--\n\n### Prebuilt and configured Entities\n\n- Enable dev in /etc/apache2/sites-available/ezrestapi.conf\n- Entities and fixtures are in place\n\n```\ngit pull\ngit checkout janit-1\ncomposer update\nphp app/console doctrine:schema:update --force\nphp app/console restapi:generate-attendees\n```\n\n- Includes some configuration and code:\n  - app/config/config.yml\n  - src/AppBundle/Entity/Attendee.php\n  - src/AppBundle/Repository/AttendeeRepository.php\n- Attendees dump in: http://ezrestapi.websc/attendees\n\n--\n\n### Install Postman\n\n - Install Postman\n - https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop\n - http://ezrestapi.websc/api/ezp/v2/attendees\n   - accept: application/vnd.ez.api.Attendee+json\n\n--\n\n### Extend controller with new route\n\n- src/AppBundle/Controller/AttendeesController.php\n\n```\npublic function listAllAction()\n{\n\n    $em = $this-\u003econtainer-\u003eget('doctrine.orm.default_entity_manager');\n\n    $attendees = $em-\u003egetRepository('AppBundle:Attendee')-\u003efindAll();\n\n    return new AttendeeList($attendees);\n}\n```\n\n--\n\n### Add route\n\n- src/AppBundle/Resources/config/routing_rest.yml\n\n```\nlist_attendees:\n    path:     /attendees\n    defaults: { _controller: AppBundle:Attendees:listAll }\n    methods: [GET]\n```\n\n--\n\n### Add value\n\n- src/AppBundle/Rest/values/AttendeeList.php\n\n```\n\u003c?php\n\nnamespace AppBundle\\Rest\\values;\n\nclass AttendeeList\n{\n    public $attendees;\n\n    public function __construct( $attendees )\n    {\n        $this-\u003eattendees = $attendees;\n    }\n}\n```\n\n - Also add namespace to Controller\n\n```\nuse AppBundle\\Rest\\values\\AttendeeList;\n```\n\n--\n\n### Configure Value Object Visitor\n\n- app/config/services.yml\n\n```\napp.rest.value_object_visitor.attendee_list:\n  parent: ezpublish_rest.output.value_object_visitor.base\n  class: AppBundle\\Rest\\valueObjectVisitor\\AttendeeList\n  tags:\n      - { name: ezpublish_rest.output.value_object_visitor, type: AppBundle\\Rest\\values\\AttendeeList }\n```\n\n--\n\n### Add Value Object Visitor\n\n- src/AppBundle/Rest/valueObjectVisitor/AttendeeList.php\n\n```\n\u003c?php\n\nnamespace AppBundle\\Rest\\valueObjectVisitor;\n\nuse eZ\\Publish\\Core\\REST\\Common\\Output\\ValueObjectVisitor;\nuse eZ\\Publish\\Core\\REST\\Common\\Output\\Generator;\nuse eZ\\Publish\\Core\\REST\\Common\\Output\\Visitor;\n\nclass AttendeeList extends ValueObjectVisitor\n{\n    public function visit( Visitor $visitor, Generator $generator, $data )\n    {\n\n        $generator-\u003estartObjectElement('AttendeeList');\n        $generator-\u003estartList('Attendees');\n\n        foreach($data-\u003eattendees as $attendee){\n            $generator-\u003estartObjectElement('Attendee');\n            $generator-\u003estartValueElement('id', $attendee-\u003egetId());\n            $generator-\u003eendValueElement('id');\n            $generator-\u003estartValueElement('name', $attendee-\u003egetName());\n            $generator-\u003eendValueElement('name');\n            $generator-\u003eendObjectElement('Attendee');\n        }\n\n        $generator-\u003eendList('Attendees');\n        $generator-\u003eendObjectElement('AttendeeList');\n    }\n}\n```\n\n--\n\n### CURL to get attendees as JSON\n\n - Voilà! Our entities via eZ REST framework:\n```\ncurl -X GET \\\n  http://ezrestapi.websc/api/ezp/v2/attendees \\\n  -H 'accept: application/vnd.ez.api.Attendee+json'\n```\n \n - Complete code is in branch: \"janit-2\"\n\n---\n\n## Using GraphQL\u003cbr /\u003ewith eZ Platform\n\n--\n\n### Introduction to GraphQL\n\n - An alternative approach to REST\n   - A complete specification, http://graphql.org\n - All requests sent as HTTP POSTs\n   - Not verbs (GET, POST, PATCH...) like in REST\n   - Reads are \"Queries\"\n   - Writes are \"Mutations\"\n - Caching tricky, requires new techniques\n\n--\n\n### Introduction to GraphQL\n\n - GraphQL is a query language (+ client/server)\n   - The syntax looks like JSON without data, just the keys\n - Strongly typed - provides some novel features\n - Often demoed with the \u003ca href=\"https://github.com/graphql/graphiql\"\u003eGraphiQL\u003c/a\u003e client app\n\n--\n\n### Quick GraphQL hands on\n\n - http://www.graphqlhub.com/playground\n\n```\n{\n  giphy {\n    search(query: \"cat\") {\n      id\n      url\n    }\n  }\n}\n```\n\n--\n\n### eZ Platform and GraphQL\n\n - There are PHP libraries and Symfony integrations\n - There is an experimental GraphQL Bundle for eZ Platform\n   - Builds on existing code (Onyx library \u0026 Overblog bundle)\n   - Written by Bertrand Dunogier from eZ Systems\n - Bundle exposes the Public PHP API via GraphQL\n    - It's \"just\" a mapping, so it's already very stable\n - https://github.com/bdunogier/ezplatform-graphql-bundle\n\n--\n\n### Installing GraphQL Bundle\n\n - https://github.com/bdunogier/ezplatform-graphql-bundle\n   1. Require with Composer\n   2. Enable Bundle in AppKernel\n   3. Add configuration*\n   4. Add routing\n\n\u003cp\u003e*Old GraphiQL version, remember to specify\u003c/p\u003e\n\n```\noverblog_graphql:\n  versions:\n    graphiql: 0.11\n```\n\n--\n\n### Hands on with eZ Platform GraphQL\n\n - Checkout step 3 and run composer update\n\n```\ngit checkout janit-3\ncomposer update\n```\n \n - After this you can hit GraphiQL client at:\u003cbr \u003ehttp://ezrestapi.websc/graphiql\n\n--\n\n### A quick look at GraphiQL \u0026 Docs\n\n - Before writing anything a query look at Docs button\n - Select \"Query\" and you'll see all available queries\n   - Terms here are eZ Platform specific\n   - This is the API documentation\n - Select locationChildren\n   - It takes \"id\" as an argument\n   - Output is a \u003cb\u003eLocation\u003c/b\u003e object\n - Click location\n    - All attributes available\n    - Includes embedded \u003cb\u003eContent\u003c/b\u003e object\n\n--\n\n### Write an eZ Platform query in GraphQL\n\n - \"We want the days, and the sessions' title \u0026 description\"\n - Start with a simple query:\n\n```\n{locationChildren}\n```\n \n - Execute. Response is null, because we don't have an id.\n - To add parent id, add it:\n\n```\n{locationChildren(id:2) {\n  id\n}}\n```\n\n - Now response is the IDs of child locations\n\n--\n\n### Write an eZ Platform query in GraphQL\n\n - We've now got the IDs, but we want the name too\n - To get the name we'll want to get the \u003cb\u003eContent\u003c/b\u003e object\n - From the object we'll want to select \u003cb\u003eName\u003c/b\u003e, but not the ID\n \n```\n{locationChildren(id:2) {\n  id,\n  content {\n    name\n  }\n}}\n```\n\n - The response now contains the names, nested\n\n--\n\n### Write an eZ Platform query in GraphQL\n\n - Finally we'll want content for the child locations\n - Queries can be nested. Let's get the children of the days:\n \n```\n{locationChildren(id:2) {\n  id,\n  content {\n    name\n  },\n  children {\n    id,\n    content {\n      name\n    }\n  } \n}}\n```\n\n - The response now contains the session names\n\n--\n\n### Write an eZ Platform query in GraphQL\n\n - Finally pull the description field from the child objects:\n \n```\n{locationChildren(id:2) {\n  id,\n  content {\n    name\n  },\n  children {\n    id,\n    content {\n      name,\n      fields(identifier:[\"description\"]) {\n        value {\n          text\n        }\n      }\n    }\n  } \n}}\n```\n\n - There's more features, but you can see the APIdoc\n\n---\n\n## Extend the GraphQL backend\u003cbr /\u003ewith a custom endpoint\n\n--\n\n### Expose Doctrine Entities via GraphQL\n\n - We can extend the GraphQL backend with new queries\n - You can add a separate endpoint or extend the eZ one\n   - For the sake of argument, we'll add a new one\n   - http://ezrestapi.websc/graphiql/attendee\n - Extend the Overblog bundle with configuration+code\n - Overblog GraphQLBundle: https://github.com/overblog/GraphQLBundle\n\n--\n\n### Update the GraphQL bundle (optional)\n\n - OPTIONAL: Upgrade to my fork with latest GraphQL Bundle\n - Update the bundle to use my fork via Composer.json:\n\n```\n    \"repositories\": [\n        {\n            \"type\": \"vcs\",\n            \"url\": \"https://github.com/janit/ezplatform-graphql-bundle\"\n        }\n```\n\n - After that, update packages:\n\n```\ncomposer update\n```\n\n--\n\n### Define a type for attendee\n\n - src/AppBundle/Resources/config/graphql/Attendee.types.yml:\n\n```\nAttendee:\n    type: object\n    config:\n        description: \"A query to return Attendee\"\n        fields:\n            id:\n                type: \"Int!\"\n                description: \"The unique ID of the attendee.\"\n            name:\n                type: \"String\"\n                description: \"Name of the attendee\"\n            country:\n                type: \"String\"\n                description: \"Country of the Attendee\"\n            bio:\n                type: \"String\"\n                description: \"Bio of the Attendee\"\n```\n\n - This defines our structure and doubles as documentation\n\n--\n\n### Define a schema for our query\n\n - src/AppBundle/Resources/config/graphql/Query.types.yml:\n\n```\nAttendeesQuery:\n    type: object\n    config:\n        description: \"Attendee ORM repository\"\n        fields:\n            attendee:\n                type: \"Attendee\"\n                args:\n                    id:\n                        description: \"Resolves using the attendee id.\"\n                        type: \"Int\"\n                resolve: \"@=resolver('Attendee', [args])\"\n```\n\n - This is picked up by convention, and registers \u003cb\u003eAttendeesQuery\u003c/b\u003e. It also configures a \u003cb\u003eresolver\u003c/b\u003e for our custom Attendee type. We will write the resolver in PHP.\n\n--\n\n### Configure an endpoint for attendees\n\n - The bundle allows \u003ca href=\"https://github.com/overblog/GraphQLBundle/blob/master/Resources/doc/definitions/schema.md#multiple-schema-endpoint\"\u003econfiguring multiple endpoints\u003c/a\u003e\n - app/config/config.yml:\n\n```\noverblog_graphql:\n    definitions:\n        internal_error_message: \"An error occurred, please retry later or contact us!\"\n        config_validation: %kernel.debug%\n        schema:\n            ez:\n                query: Query\n                mutation: PlatformMutation\n            attendees:\n                query: AttendeesQuery\n    versions:\n        graphiql: 0.11\n```\n\n - GraphiQL: http://ezrestapi.websc/graphiql/attendees\n\n--\n\n### Add a resover for Attendee\n\n- src/AppBundle/GraphQL/Resolver/AttendeeResolver.php\n\n```\n\u003c?php\n\nnamespace AppBundle\\GraphQL\\Resolver;\n\nuse Doctrine\\ORM\\EntityManager;\nuse Overblog\\GraphQLBundle\\Resolver\\ResolverInterface;\n\nclass AttendeeResolver implements ResolverInterface {\n\n    private $em;\n\n    public function __construct(EntityManager $em)\n    {\n        $this-\u003eem = $em;\n    }\n\n    public function resolve($input)\n    {\n\n        $args = $input-\u003egetRawArguments();\n\n        $attendee = $this-\u003eem-\u003egetRepository('AppBundle:Attendee')-\u003efind($args['id']);\n\n        return [\n            'id' =\u003e $attendee-\u003egetId(),\n            'name' =\u003e $attendee-\u003egetName(),\n            'country' =\u003e $attendee-\u003egetCountry(),\n            'bio' =\u003e $attendee-\u003egetBio()\n        ];\n\n    }\n}\n```\n\n- Like a ValueObjectVisitor in eZ REST, you can use backend. A prepared eZ Query is fine, but we'll use Doctrine ORM.\n\n--\n\n### Register resolver as a service\n\n - app/config/services.yml:\n\n```\napp.graphql.attendee:\n    class: AppBundle\\GraphQL\\Resolver\\AttendeeResolver\n    arguments: ['@doctrine.orm.default_entity_manager']\n    tags:\n        - { name: overblog_graphql.resolver, method: resolve, alias: 'Attendee' }\n```\n\n--\n\n### Fetching Individuals\n\n - Fetching individual attendees\n\n```\n{attendee(id:1) {\n  id,\n  name,\n  country,\n  bio\n}}\n```\n\n - You can check out the complete code:\n\n```\ngit checkout janit-4\n```\n\n--\n\n### Defining AttendeeList type\n\n - To get a list of attendees, we will add a query that will embed \u003cb\u003eAttendee\u003c/b\u003e objects. Let's start with the type definition.\n - src/AppBundle/Resources/config/graphql/AttendeeList.types.yml:\n\n```\nAttendeeList:\n    type: object\n    config:\n        description: \"A query to return list of Attendees\"\n        fields:\n            id:\n                type: \"String!\"\n                description: \"The unique hash of the attendee query params.\"\n            attendees:\n                type: \"[Attendee]\"\n                description: \"Attendee info\"\n```\n\n - The \u003cb\u003eattendees field\u003c/b\u003e is an array of \u003cb\u003eAttendee objects\u003c/b\u003e\n\n--\n\n### Defining attendee query\n\n - We need to expose a new query, so it is available.\n - src/AppBundle/Resources/config/graphql/Query.types.yml:\n\n```\nattendee_list:\n    type: \"AttendeeList\"\n    args:\n        limit:\n            description: \"limit\"\n            type: \"Int\"\n    resolve: \"@=resolver('AttendeeList', [args])\"\n```\n\n - The resolver is configured and needs a resolver in PHP\n\n--\n\n### Add AttendeeList Resolver\n\n - The resolver lists all attendees and returns a nested array\n - src/AppBundle/GraphQL/Resolver/AttendeeListResolver.php:\n\n```\n\u003c?php\n\nnamespace AppBundle\\GraphQL\\Resolver;\n\nuse Doctrine\\ORM\\EntityManager;\nuse Overblog\\GraphQLBundle\\Resolver\\ResolverInterface;\n\nclass AttendeeListResolver implements ResolverInterface {\n\n    private $em;\n\n    public function __construct(EntityManager $em)\n    {\n        $this-\u003eem = $em;\n    }\n\n    public function resolve($input)\n    {\n\n        $args = $input-\u003egetRawArguments();\n\n        $attendees = $this-\u003eem-\u003egetRepository('AppBundle:Attendee')-\u003efindAll();\n\n        $attendeeList = [];\n\n        foreach($attendees as $attendee){\n\n            $attendeeList[] = [\n                'id' =\u003e $attendee-\u003egetId(),\n                'name' =\u003e $attendee-\u003egetName(),\n                'country' =\u003e $attendee-\u003egetCountry(),\n                'bio' =\u003e $attendee-\u003egetBio()\n            ];\n\n        }\n\n        return [\n            'id' =\u003e md5(serialize($args)),\n            'attendees' =\u003e $attendeeList\n        ];\n\n    }\n\n}\n```\n\n - The limit argument is not used, but it is available in \u003cb\u003e$args\u003c/b\u003e\n\n--\n\n### Register resolver as service\n\n- app/config/services.yml:\n\n```\napp.graphql.attendee_list:\n        class: AppBundle\\GraphQL\\Resolver\\AttendeeListResolver\n        arguments: ['@doctrine.orm.default_entity_manager']\n        tags:\n            - { name: overblog_graphql.resolver, method: resolve, alias: 'AttendeeList' }\n```\n\n--\n\n### Query AttendeeList of Attendee objs\n\n - In GraphiQL run a query to get all the names of attendees:\n\n```\n{attendee_list {\n  attendees{\n    name\n  }\n}}\n```\n\n - To get the full working code, check out branch janit-5:\n\n```\ngit checkout janit-5\n```\n\n--\n\n### More GraphQL resources\n\n - https://symfony.fi/entry/graphql-with-php-and-the-symfony-framework\n - https://www.symfony.fi/entry/what-is-graphql-and-how-does-it-differ-from-rest-apis\n - http://graphql.org/graphql-js/\n - https://github.com/Youshido/GraphQLBundle/\n\n---\n\n## The end\n\n- Thank you","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjanit%2Fwsc17-slides","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjanit%2Fwsc17-slides","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjanit%2Fwsc17-slides/lists"}