{"id":14975996,"url":"https://github.com/mghoneimy/php-graphql-oqm","last_synced_at":"2025-04-09T13:05:59.254Z","repository":{"id":41086376,"uuid":"178165924","full_name":"mghoneimy/php-graphql-oqm","owner":"mghoneimy","description":"A PHP library which generates query object classes from API schema declaration and uses these objects to interact with GraphQL servers in a simple and intuitive way.","archived":false,"fork":false,"pushed_at":"2024-03-19T14:53:25.000Z","size":81,"stargazers_count":42,"open_issues_count":15,"forks_count":24,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-02T11:05:39.138Z","etag":null,"topics":["class-generator","graphql","graphql-api","graphql-client","query-generator"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mghoneimy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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":"2019-03-28T09:04:24.000Z","updated_at":"2025-03-26T11:32:11.000Z","dependencies_parsed_at":"2024-06-18T18:13:48.459Z","dependency_job_id":"a936160e-40cf-48fd-8233-e64600cb42ef","html_url":"https://github.com/mghoneimy/php-graphql-oqm","commit_stats":{"total_commits":41,"total_committers":5,"mean_commits":8.2,"dds":0.5365853658536586,"last_synced_commit":"2b451e300f163c6c576a28bc10f80a45d53cb765"},"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mghoneimy%2Fphp-graphql-oqm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mghoneimy%2Fphp-graphql-oqm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mghoneimy%2Fphp-graphql-oqm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mghoneimy%2Fphp-graphql-oqm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mghoneimy","download_url":"https://codeload.github.com/mghoneimy/php-graphql-oqm/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248045231,"owners_count":21038553,"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","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":["class-generator","graphql","graphql-api","graphql-client","query-generator"],"created_at":"2024-09-24T13:53:06.532Z","updated_at":"2025-04-09T13:05:59.229Z","avatar_url":"https://github.com/mghoneimy.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PHP GraphQL OQM\n![Build Status](https://github.com/mghoneimy/php-graphql-oqm/actions/workflows/test-and-cover-linux.yml/badge.svg)\n![Build Status](https://github.com/mghoneimy/php-graphql-oqm/actions/workflows/test-windows-macos.yml/badge.svg)\n[![Codacy Badge](https://app.codacy.com/project/badge/Coverage/426f75816a2d43d3a2a5df4f13b15f6b)](https://app.codacy.com/gh/mghoneimy/php-graphql-oqm/dashboard?utm_source=gh\u0026utm_medium=referral\u0026utm_content=\u0026utm_campaign=Badge_coverage)\n[![Total\nDownloads](https://poser.pugx.org/gmostafa/php-graphql-oqm/downloads)](https://packagist.org/packages/gmostafa/php-graphql-oqm)\n[![Latest Stable\nVersion](https://poser.pugx.org/gmostafa/php-graphql-oqm/v/stable)](https://packagist.org/packages/gmostafa/php-graphql-oqm)\n[![License](https://poser.pugx.org/gmostafa/php-graphql-oqm/license)](https://packagist.org/packages/gmostafa/php-graphql-oqm)\n\nThis package utilizes the introspection feature of GraphQL APIs to generate a set of classes that map to the structure\nof the API schema. The generated classes can then be used in a very simple and intuitive way to query the API server.\n\nInteracting with GraphQL API's using PHP has never been easier!\n\n# Installation\nRun the following command to install the package using composer:\n```\ncomposer require gmostafa/php-graphql-oqm\n```\n\n# Generating The Schema Objects\nAfter installing the package, the first step is to generate the schema objects. This can be easily achieved by executing\nthe following command:\n```\nphp vendor/bin/generate_schema_objects\n```\nThis script will retrieve the API schema types using the introspection feature in GraphQL, then generate the schema\nobjects from the types, and save them in the `schema_object` directory in the root directory of the package. You can\noverride the default write directory by providing the \"Custom classes writing dir\" value when running the command.\n\nYou can also specify all options via command line options:\n\n```\nphp vendor/bin/generate_schema_objects \\\n    -u \"https://graphql-pokemon.vercel.app/\" \\\n    -h \"Authorization\" \\\n    -v \"Bearer 123\" \\\n    -d \"customClassesWritingDirectory\" \\\n    -n \"Vendor\\Custom\\Namespace\"\n```\n\nor if you prefer long arguments\n\n```\nphp vendor/bin/generate_schema_objects \\\n    --url \"https://graphql-pokemon.vercel.app/\" \\\n    --authorization-header-name \"Authorization\" \\\n    --authorization-header-value \"Bearer 123\" \\\n    --directory \"customClassesWritingDirectory\" \\\n    --namespace \"Vendor\\Custom\\Namespace\"\n```\n\n# Usage\nIn all the examples below I'm going to use the super cool public Pokemon GraphQL API as an illustration.\n\nCheck out the API at: https://graphql-pokemon.now.sh/\n\nAnd Github Repo: https://github.com/lucasbento/graphql-pokemon\n\nAfter generating the schema objects for the public Pokemon API, we can easily query the API by using the\n`RootQueryObject`. Here's an example:\n```\n$rootObject = new RootQueryObject();\n$rootObject\n    -\u003eselectPokemons((new RootPokemonsArgumentsObject())-\u003esetFirst(5))\n        -\u003eselectName()\n        -\u003eselectId()\n        -\u003eselectFleeRate()\n        -\u003eselectAttacks()\n            -\u003eselectFast()\n                -\u003eselectName();\n```\nWhat this query does is that it selects the first 5 pokemons returning their names, ids, flee rates, fast attacks with\ntheir names. Easy right!?\n\nAll what remains is that we actually run the query to obtain results:\n```\n$results = $client-\u003erunQuery($rootObject-\u003egetQuery());\n``` \nFor more on how to use the client class refer to:\n- https://github.com/mghoneimy/php-graphql-client#constructing-the-client\n- https://github.com/mghoneimy/php-graphql-client#running-queries\n\n## Notes\nA couple of notes about schema objects to make your life easier when using the generating classes:\n\n### Dealing With Object Selectors\nWhilst scalar field setters return an instance of the current query object, object field selectors return objects of\nthe nested query object. This means that setting the `$rootObject` reference to the result returned by an object\nselector means that the root query object reference is gone.\n\nDon't:\n```\n$rootObject = (new RootQueryObject())-\u003eselectAttacks()-\u003eselectSpecial()-\u003eselectName();\n```\nThis way you end up with reference to the `PokemonAttackQueryObject`, and the reference to the `RootQueryObject` is gone.\n\nDo:\n```\n$rootObjet = new RootQueryObject();\n$rootObject-\u003eselectAttacks()-\u003eselectSpecial()-\u003eselectName();\n```\nThis way you can keep track of the `RootQueryObject` reference and develop your query safely.\n\n### Dealing With Multiple Object Selectors\nSuppose we want to get the pokemon \"Charmander\", retrieve his evolutions, evolution requirements, and evolution\nrequirements of his evolutions, how can we do that?\n\nWe can't do this:\n```\n$rootObject = new RootQueryObject();\n$rootObject-\u003eselectPokemon(\n    (new RootPokemonArgumentsObject())-\u003esetName('charmander')\n)\n    -\u003eselectEvolutions()\n        -\u003eselectName()\n        -\u003eselectNumber()\n        -\u003eselectEvolutionRequirements()\n            -\u003eselectName()\n            -\u003eselectAmount()\n    -\u003eselectEvolutionRequirements()\n        -\u003eselectName()\n        -\u003eselectAmount();\n```\nThis is because the reference is now pointing to the evolution requirements of the evolutions of charmander and not\ncharmander himself.\n\nThe best way to do this is by structuring the query like this:\n```\n$rootObject = new RootQueryObject();\n$charmander = $rootObject-\u003eselectPokemon(\n    (new RootPokemonArgumentsObject())-\u003esetName('charmander')\n);\n$charmander-\u003eselectEvolutions()\n    -\u003eselectName()\n    -\u003eselectNumber()\n    -\u003eselectEvolutionRequirements()\n        -\u003eselectName()\n        -\u003eselectAmount();\n$charmander-\u003eselectEvolutionRequirements()\n    -\u003eselectName()\n    -\u003eselectAmount();\n```\nThis way we have kept the reference to charmander safe and constructed our query in an intuitive way.\n\nGenerally, whenever there's a branch off (just like in the case of getting evolutions and evolution requirements of the\nsame object) the best way to do it is to structure the query like a tree, where the root of the tree becomes the\nreference to the object being branch off from. In this case, charmander is the root and evolutions and evolution\nrequirements are 2 sub-trees branched off it.\n\n### Improving Query Objects Readability\nA couple of hints on how to keep your query objects more readable:\n1. Store nodes that will be used as roots in branch offs in meaningful variables, just like the case with charmander.\n2. Write each selector on a separate line.\n3. Every time you use an object selector, add an extra indentation to the next selectors.\n4. Move construction of a new object in the middle of a query (such as an ArgumentsObject construction) to a new line.\n\n# Schema Objects Generation\nAfter running the generation script, the SchemaInspector will run queries on the GraphQL server to retrieve the API\nschema. After that, the SchemaClassGenerator will traverse the schema from the root queryType recursively, creating\na class for every object in the schema spec.\n\nThe SchemaClassGenerator will generate a different schema object depending on the type of object being scanned using the\nfollowing mapping from GraphQL types to SchemaObject types:\n- OBJECT: `QueryObject`\n- INPUT_OBJECT: `InputObject`\n- ENUM: `EnumObject`\n\nAdditionally, an `ArgumentsObject` will be generated for the arguments on each field in every object. The arguments\nobject naming convention is:\n\n`{CURRENT_OBJECT}{FIELD_NAME}ArgumentsObject`\n\n## The QueryObject\nThe object generator will start traversing the schema from the root `queryType`, creating a class for each query object\nit encounters according to the following rules:\n- The `RootQueryObject` is generated for the type corresponding to the `queryType` in the schema declaration, this\nobject is the start of all GraphQL queries.\n- For a query object of name {OBJECT_NAME}, a class with name `{OBJECT_NAME}QueryObject` will be created.\n- For each selection field in the selection set of the query object, a corresponding selector method will be created,\naccording to the following rules:\n  - Scalar fields will have a simple selector created for them, which will add the field name to the selection set.\n  The simple selector will return a reference to the query object being created (this).\n  - Object fields will have an object selector created for them, which will create a new query object internally and\n  nest it inside the current query. The object selector will return instance of the new query object created.\n- For every list of arguments tied to an object field an `ArgumentsObject` will be created with a setter corresponding\nto every argument value according to the following rules:\n  - Scalar arguments: will have a simple setter created for them to set the scalar argument value.\n  - List arguments: will have a list setter created for them to set the argument value with an `array`\n  - Input object arguments: will have an input object setter created for them to set the argument value with an object\n  of type `InputObject`\n\n## The InputObject\nFor every input object the object generator encounters while traversing the schema, it will create a corresponding class\naccording to the following rules:\n- For an input object of name {OBJECT_NAME}, a class with name `{OBJECT_NAME}InputObject` will be created\n- For each field in the input object declaration, a setter will be created according to the following rules:\n  - Scalar fields: will have a simple setter created for them to set the scalar value.\n  - List fields: will have a list setter created for them to set the value with an `array`\n  - Input object arguments: will have an input object setter created for them to set the value with an object of type\n  `InputObject`\n\n## The EnumObject\nFor every enum the object generator encounters while traversing the schema, it will create a corresponding ENUM class\naccording to the following rules:\n- For an enum object of name {OBJECT_NAME}, a class with name `{OBJECT_NAME}EnumObject` will be created\n- For each EnumValue in the ENUM declaration, a const will be created to hold its value in the class\n\n# Live API Example\nLooking at the schema of the Pokemon GraphQL API from the root queryType, that' how it looks like:\n```\n\"queryType\": {\n  \"name\": \"Query\",\n  \"kind\": \"OBJECT\",\n  \"description\": \"Query any Pokémon by number or name\",\n  \"fields\": [\n    {\n      \"name\": \"query\",\n      \"type\": {\n        \"name\": \"Query\",\n        \"kind\": \"OBJECT\",\n        \"ofType\": null\n      },\n      \"args\": []\n    },\n    {\n      \"name\": \"pokemons\",\n      \"type\": {\n        \"name\": null,\n        \"kind\": \"LIST\",\n        \"ofType\": {\n          \"name\": \"Pokemon\",\n          \"kind\": \"OBJECT\"\n        }\n      },\n      \"args\": [\n        {\n          \"name\": \"first\",\n          \"description\": null\n        }\n      ]\n    },\n    {\n      \"name\": \"pokemon\",\n      \"type\": {\n        \"name\": \"Pokemon\",\n        \"kind\": \"OBJECT\",\n        \"ofType\": null\n      },\n      \"args\": [\n        {\n          \"name\": \"id\",\n          \"description\": null\n        },\n        {\n          \"name\": \"name\",\n          \"description\": null\n        }\n      ]\n    }\n  ]\n}\n```\nWhat we basically have is a root query object with 2 fields:\n1. pokemons: Retrieves a list of Pokemon objects. It has one argument: first.\n2. pokemon: Retrieves one Pokemon object. It has two arguments:: id and name.\n\nTranslating this small part of the schema leads to 3 objects:\n1. RootQueryObject: Represents the entry to point to traversing the API graph\n2. RootPokemonsArgumentsObject: Represents the arguments list on the \"pokemons\" field in the RootQueryObject\n3. RootPokemonArgumentsObject: Represents the arguments list on the \"pokemon\" field in the RootQueryObject\n\nHere are the 3 classes generated:\n```\n\u003c?php\n\nnamespace GraphQL\\SchemaObject;\n\nclass RootQueryObject extends QueryObject\n{\n    const OBJECT_NAME = \"query\";\n\n    public function selectPokemons(RootPokemonsArgumentsObject $argsObject = null)\n    {\n        $object = new PokemonQueryObject(\"pokemons\");\n        if ($argsObject !== null) {\n            $object-\u003eappendArguments($argsObject-\u003etoArray());\n        }\n        $this-\u003eselectField($object);\n    \n        return $object;\n    }\n\n    public function selectPokemon(RootPokemonArgumentsObject $argsObject = null)\n    {\n        $object = new PokemonQueryObject(\"pokemon\");\n        if ($argsObject !== null) {\n            $object-\u003eappendArguments($argsObject-\u003etoArray());\n        }\n        $this-\u003eselectField($object);\n    \n        return $object;\n    }\n}\n```\nThe `RootQueryObject` contains 2 selector methods, one for each field, and an optional argument containing the\nArgumentsObjects required.\n\n```\n\u003c?php\n\nnamespace GraphQL\\SchemaObject;\n\nclass RootPokemonsArgumentsObject extends ArgumentsObject\n{\n    protected $first;\n\n    public function setFirst($first)\n    {\n        $this-\u003efirst = $first;\n    \n        return $this;\n    }\n}\n```\nThe `RootPokemonsArgumentsObject` contains the only argument in the list for the \"pokemons\" field as a property with a\nsetter for altering its value. \n\n```\n\u003c?php\n\nnamespace GraphQL\\SchemaObject;\n\nclass RootPokemonArgumentsObject extends ArgumentsObject\n{\n    protected $id;\n    protected $name;\n\n    public function setId($id)\n    {\n        $this-\u003eid = $id;\n    \n        return $this;\n    }\n\n    public function setName($name)\n    {\n        $this-\u003ename = $name;\n    \n        return $this;\n    }\n}\n```\nThe `RootPokemonArgumentsObject` contains the 2 arguments in the list for the \"pokemon\" field as properties with setters\nto alter their values.\n\n## Extra\nAdditionally, `PokemonQueryObject` will be created while traversing the schema recursively. It is not needed to complete\nthis demo, but I will add it below to make things clearer in case someone wants to see more of the generation in action:\n```\n\u003c?php\n\nnamespace GraphQL\\SchemaObject;\n\nclass PokemonQueryObject extends QueryObject\n{\n    const OBJECT_NAME = \"Pokemon\";\n\n    public function selectId()\n    {\n        $this-\u003eselectField(\"id\");\n    \n        return $this;\n    }\n\n    public function selectNumber()\n    {\n        $this-\u003eselectField(\"number\");\n    \n        return $this;\n    }\n\n    public function selectName()\n    {\n        $this-\u003eselectField(\"name\");\n    \n        return $this;\n    }\n\n    public function selectWeight(PokemonWeightArgumentsObject $argsObject = null)\n    {\n        $object = new PokemonDimensionQueryObject(\"weight\");\n        if ($argsObject !== null) {\n            $object-\u003eappendArguments($argsObject-\u003etoArray());\n        }\n        $this-\u003eselectField($object);\n    \n        return $object;\n    }\n\n    public function selectHeight(PokemonHeightArgumentsObject $argsObject = null)\n    {\n        $object = new PokemonDimensionQueryObject(\"height\");\n        if ($argsObject !== null) {\n            $object-\u003eappendArguments($argsObject-\u003etoArray());\n        }\n        $this-\u003eselectField($object);\n    \n        return $object;\n    }\n\n    public function selectClassification()\n    {\n        $this-\u003eselectField(\"classification\");\n    \n        return $this;\n    }\n\n    public function selectTypes()\n    {\n        $this-\u003eselectField(\"types\");\n    \n        return $this;\n    }\n\n    public function selectResistant()\n    {\n        $this-\u003eselectField(\"resistant\");\n    \n        return $this;\n    }\n\n    public function selectAttacks(PokemonAttacksArgumentsObject $argsObject = null)\n    {\n        $object = new PokemonAttackQueryObject(\"attacks\");\n        if ($argsObject !== null) {\n            $object-\u003eappendArguments($argsObject-\u003etoArray());\n        }\n        $this-\u003eselectField($object);\n    \n        return $object;\n    }\n\n    public function selectWeaknesses()\n    {\n        $this-\u003eselectField(\"weaknesses\");\n    \n        return $this;\n    }\n\n    public function selectFleeRate()\n    {\n        $this-\u003eselectField(\"fleeRate\");\n    \n        return $this;\n    }\n\n    public function selectMaxCP()\n    {\n        $this-\u003eselectField(\"maxCP\");\n    \n        return $this;\n    }\n\n    public function selectEvolutions(PokemonEvolutionsArgumentsObject $argsObject = null)\n    {\n        $object = new PokemonQueryObject(\"evolutions\");\n        if ($argsObject !== null) {\n            $object-\u003eappendArguments($argsObject-\u003etoArray());\n        }\n        $this-\u003eselectField($object);\n    \n        return $object;\n    }\n\n    public function selectEvolutionRequirements(PokemonEvolutionRequirementsArgumentsObject $argsObject = null)\n    {\n        $object = new PokemonEvolutionRequirementQueryObject(\"evolutionRequirements\");\n        if ($argsObject !== null) {\n            $object-\u003eappendArguments($argsObject-\u003etoArray());\n        }\n        $this-\u003eselectField($object);\n    \n        return $object;\n    }\n\n    public function selectMaxHP()\n    {\n        $this-\u003eselectField(\"maxHP\");\n    \n        return $this;\n    }\n\n    public function selectImage()\n    {\n        $this-\u003eselectField(\"image\");\n    \n        return $this;\n    }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmghoneimy%2Fphp-graphql-oqm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmghoneimy%2Fphp-graphql-oqm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmghoneimy%2Fphp-graphql-oqm/lists"}