Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
d03a573
feat: generate GraphQL client from schema
thiagolepidus Jun 15, 2026
dc5c7c8
refactor: simplify generated GraphQL client
thiagolepidus Jun 15, 2026
a722418
fix: harden generated GraphQL client execution
thiagolepidus Jun 15, 2026
7d95066
fix: close GraphQL client review findings
thiagolepidus Jun 15, 2026
ada7e67
feat: hydrate generated GraphQL schemas
thiagolepidus Jun 15, 2026
9e57fde
docs: update generated GraphQL client usage
thiagolepidus Jun 15, 2026
80b8e41
feat: simplify generated enum usage
thiagolepidus Jun 15, 2026
5a1612d
docs: align REST usage examples
thiagolepidus Jun 15, 2026
6006182
refactor: remove generated GraphQL namespace
thiagolepidus Jun 15, 2026
43bbc42
refactor: improve GraphQL generator readability
thiagolepidus Jun 15, 2026
ebbd1f9
feat(graphql): send operation arguments as variables
thiagolepidus Jun 16, 2026
1534016
fix(graphql): preserve response error context
thiagolepidus Jun 16, 2026
96aa116
fix(graphql): wrap transport failures
thiagolepidus Jun 16, 2026
6798347
fix(graphql): validate input object variables
thiagolepidus Jun 16, 2026
1334769
fix(graphql): validate selection fields
thiagolepidus Jun 16, 2026
5d9a73f
feat(graphql): generate phpdoc for schema accessors
thiagolepidus Jun 16, 2026
3999bf2
fix(graphql): validate enum variable values
thiagolepidus Jun 16, 2026
2e8fb57
fix(graphql): validate variable value types
thiagolepidus Jun 16, 2026
7bef109
feat(graphql): generate list item phpdoc types
thiagolepidus Jun 16, 2026
fc74867
fix(graphql): guard generated client target
thiagolepidus Jun 16, 2026
2ec1b3f
chore(graphql): regenerate schema accessors
thiagolepidus Jun 16, 2026
4856e2e
fix(graphql): validate required operation arguments
thiagolepidus Jun 16, 2026
91beab8
fix(graphql): validate operation argument names
thiagolepidus Jun 16, 2026
dea4ba0
refactor(graphql): split operation request responsibilities
thiagolepidus Jun 16, 2026
87b24df
refactor(graphql): share schema value hydration
thiagolepidus Jun 16, 2026
418f0f3
refactor(graphql): extract dynamic call argument parsing
thiagolepidus Jun 16, 2026
2af53ba
test(graphql): remove redundant raw query passthrough test
thiagolepidus Jun 16, 2026
acd24fb
feat(graphql): add generated accessors to inputs
thiagolepidus Jun 16, 2026
c6a7941
refactor(graphql): split generator into components
thiagolepidus Jun 16, 2026
c6f0109
refactor(graphql): move generator components out of entrypoint
thiagolepidus Jun 16, 2026
5d705c6
refactor(graphql): report generator failures with exceptions
thiagolepidus Jun 16, 2026
d5acf76
fix(graphql): report schema loading failures clearly
thiagolepidus Jun 16, 2026
9693beb
refactor(graphql): extract generated type class builders
thiagolepidus Jun 16, 2026
68033d6
refactor(graphql): share generated accessor method builder
thiagolepidus Jun 16, 2026
fe071b0
chore: add PHPStan analysis
thiagolepidus Jun 16, 2026
93f5102
refactor(graphql): stop generating root schema classes
thiagolepidus Jun 16, 2026
c6751f2
feat(graphql): generate client operation method phpdoc
thiagolepidus Jun 16, 2026
7212033
ci: run graphql checks across supported php versions
thiagolepidus Jun 16, 2026
3801ce9
ci: run phpstan from vendor bin in gitlab
thiagolepidus Jun 16, 2026
79b5db4
Merge branch 'generated_graphql_client' into 'main'
thiagolepidus Jun 16, 2026
f285173
fix: update vulnerable dependencies
thiagolepidus Jun 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 4 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,7 @@ jobs:
run: vendor/bin/php-cs-fixer fix -v --diff --allow-risky=yes --config=.php-cs-fixer.php

- name: Run test suite
run: vendor/bin/phpunit tests/
run: vendor/bin/phpunit tests/

- name: Run static analysis
run: composer analyse
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/vendor/
/.php-cs-fixer.cache
/.php-cs-fixer.cache
/tmp/
45 changes: 44 additions & 1 deletion .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ before_script:
.test: &test
- vendor/bin/phpunit tests/

.analyse: &analyse
- vendor/bin/phpstan analyse --debug --memory-limit=1G

lint:php7.4:
image: php:7.4
script:
Expand All @@ -30,7 +33,47 @@ test:php7.4:
script:
- *test

test:php8.0:
image: php:8.0
script:
- *test

test:php8.1:
image: php:8.1
script:
- *test

test:php8.2:
image: php:8.2
script:
- *test
- *test

test:php8.3:
image: php:8.3
script:
- *test

analyse:php7.4:
image: php:7.4
script:
- *analyse

analyse:php8.0:
image: php:8.0
script:
- *analyse

analyse:php8.1:
image: php:8.1
script:
- *analyse

analyse:php8.2:
image: php:8.2
script:
- *analyse

analyse:php8.3:
image: php:8.3
script:
- *analyse
239 changes: 142 additions & 97 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,55 +24,85 @@ $client = new \ThothApi\GraphQL\Client();

#### Queries

The client maps all queries from the Thoth GraphQL API. Methods return data in an object-oriented format, making it easy to use and manipulate information.

```php
$contributors = $client->contributors();

echo print_r($contributors, true);
/**
* Array (
* [0] => ThothApi\GraphQL\Models\Contributor Object (
* [data] => Array (
* [contributorId] => e1de541c-e84b-4092-941f-dab9b5dac865
* [firstName] => Aaron
* [lastName] => Ansell
* [fullName] => Aaron Ansell
* [orcid] => https://orcid.org/0000-0001-6365-5168
* [website] =>
* )
* )
* [1] => ThothApi\GraphQL\Models\Contributor Object (
* [data] => Array (
* [contributorId] => 1c3aade6-6d48-41b4-8def-b435f4b43573
* [firstName] => Aaron D.
* [lastName] => Hornkohl
* [fullName] => Aaron D. Hornkohl
* [orcid] =>
* [website] => https://www.ames.cam.ac.uk/people/dr-aaron-d-hornkohl
* )
* )
* ...
* )
*/

$contributor = array_shift($contributors);

echo $contributor->getLastName(); // Ansell
echo $contributor->getOrcid(); // https://orcid.org/0000-0001-6365-5168
```

Queries can accept an array with the required arguments as specified in the Thoth GraphQL schema. It's possible to use the "order" argument specifying only the field and the desired direction.
Query, mutation, schema, input, enum and scalar classes are generated from the Thoth GraphQL
introspection schema. The most convenient API is the dynamic client method named after the GraphQL
operation.

```php
$works = $client->works(5, [
'workId',
'fullTitle',
]);

echo $works[0]->getWorkId();
echo $works[0]->getFullTitle();
```

Arguments can be passed positionally or by name. Enum values can be passed directly using generated
enum constants. `OperationRequest::enum()` is still supported for custom operations.

```php
use ThothApi\GraphQL\Enums\Direction;
use ThothApi\GraphQL\Enums\WorkField;

$works = $client->works([
'publishers' => ['71faf1c3-900a-4b8c-bca7-4f927699fb90'],
'limit' => 5,
'field' => 'PUBLICATION_DATE',
'direction' => 'DESC'
'order' => [
'field' => WorkField::PUBLICATION_DATE,
'direction' => Direction::DESC,
],
], [
'workId',
'fullTitle',
'doi',
]);
```

Selections can include nested fields. Returned object fields and lists are hydrated into generated
schema classes.

```php
$work = $client->work('5a5b0fe3-03a9-444b-b221-ecae5370ff30', [
'workId',
'fullTitle',
'titles' => [
'titleId',
'fullTitle',
],
'imprint' => [
'imprintId',
'publisher' => [
'publisherId',
'publisherName',
],
],
]);

echo $work->getImprint()->getPublisher()->getPublisherName();
echo $work->getTitles()[0]->getFullTitle();
```

The generic executor is still available when you want to use the generated operation classes
directly. It returns raw arrays instead of hydrated schema objects.

```php
use ThothApi\GraphQL\Queries\WorksQuery;

$works = $client->execute(WorksQuery::operation([
'limit' => 5,
], [
'workId',
'fullTitle',
]));
```

Raw GraphQL queries are supported and use the configured token.

```php
$data = $client->rawQuery('query { workCount }');
```

#### Mutations

To execute mutations, provide a valid personal access token to the client.
Expand All @@ -81,20 +111,60 @@ To execute mutations, provide a valid personal access token to the client.
$client->setToken($token);
```

Mutations can be executed by providing an instance of the model class corresponding to the mutation type. To delete mutations, only the object's ID is required. When the operation is successful, the object's ID is returned.
Inputs are generated from the schema. They can be created from arrays, from DTOs that expose
`getAllData()`, or from `JsonSerializable` objects.

```php
use ThothApi\GraphQL\Enums\SubjectType;
use ThothApi\GraphQL\Inputs\NewSubject;

$newSubject = new NewSubject([
'workId' => '5a5b0fe3-03a9-444b-b221-ecae5370ff30',
'subjectType' => SubjectType::BIC,
'subjectCode' => '1D',
'subjectOrdinal' => 3,
]);

$subjectId = $client->createSubject($newSubject);
```

By default, mutations returning objects select the first `*Id` field and return that scalar value, so
existing calls stay short.

```php
$subjectId = $client->createSubject($newSubject);
```

Pass an explicit selection when you want a hydrated schema object with getters, setters and `toArray()`.

```php
$subject = $client->createSubject($newSubject, [
'subjectId',
'subjectCode',
]);

echo $subject->getSubjectId();
echo $subject->getSubjectCode();
print_r($subject->toArray());
```

Schema objects can also be instantiated and populated manually.

```php
use ThothApi\GraphQL\Models\Subject;
use ThothApi\GraphQL\Schemas\Work;

$work = (new Work())
->setWorkId('5a5b0fe3-03a9-444b-b221-ecae5370ff30')
->setFullTitle('Example title');

$subject = new Subject();
$subject->setWorkId('5a5b0fe3-03a9-444b-b221-ecae5370ff30');
$subject->setSubjectType(Subject::SUBJECT_TYPE_BIC);
$subject->setSubjectCode('1D');
$subject->setSubjectOrdinal(3);
echo $work->getFullTitle();
print_r($work->toArray());
```

$subjectId = $client->createSubject($subject); // 1d5ae47b-9e0c-4fba-b2d4-a3a2cdd8860c
Regenerate the GraphQL classes from the current Thoth GraphQL schema:

$client->deleteSubject($subjectId);
```bash
composer generate-graphql-client
```

#### Exceptions
Expand All @@ -103,10 +173,11 @@ A QueryException is thrown in case of an error in the request to the GraphQL API

```php
try {
$work = new \ThothApi\GraphQL\Models\Work([
'doi' => 'https://doi.org/10.00000/00000000',
]);
$workId = $client->createWork($work);
$client->execute(\ThothApi\GraphQL\Mutations\CreateWorkMutation::operation([
'data' => [
'doi' => 'https://doi.org/10.00000/00000000',
],
], ['workId']));
} catch (\ThothApi\Exception\QueryException $exception) {
echo $exception->getMessage();
/**
Expand All @@ -125,6 +196,8 @@ try {
* )
* )
*/
echo $exception->getQuery();
echo print_r($exception->getVariables(), true);
}
```

Expand All @@ -134,44 +207,17 @@ API Documentation: https://export.thoth.pub/

```php
$client = new \ThothApi\Rest\Client();
```

The REST client exports metadata in the formats exposed by the Thoth export API.

```php
$formats = $client->formats();

echo(print_r($client->work('doideposit::crossref', 'e0f748b2-984f-45cc-8b9e-13989c31dda4'), true));
/**
* <?xml version="1.0" encoding="utf-8"?>
* <doi_batch version="5.3.1" xmlns="http://www.crossref.org/schema/5.3.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.crossref.org/schema/5.3.1 http://www.crossref.org/schemas/crossref5.3.1.xsd" xmlns:ai="http://www.crossref.org/AccessIndicators.xsd" xmlns:jats="http://www.ncbi.nlm.nih.gov/JATS1" xmlns:fr="http://www.crossref.org/fundref.xsd">
* <head>
* <doi_batch_id>e0f748b2-984f-45cc-8b9e-13989c31dda4_20241010195624</doi_batch_id>
* <timestamp>20241010195624</timestamp>
* <depositor>
* <depositor_name>Thoth</depositor_name>
* <email_address>distribution@thoth.pub</email_address>
* </depositor>
* <registrant>Thoth</registrant>
* </head>
* <body>
* <book book_type="monograph">
* <book_metadata language="en">
* <contributors>
* <person_name sequence="first" contributor_role="author">
* <given_name>Ammiel</given_name>
* <surname>Alcalay</surname>
* <affiliations>
* <institution>
* <institution_name>Queens College, CUNY</institution_name>
* <institution_id type="ror">https://ror.org/03v8adn41</institution_id>
* </institution>
* <institution>
* <institution_name>The Graduate Center, CUNY</institution_name>
* <institution_id type="ror">https://ror.org/00awd9g61</institution_id>
* </institution>
* </affiliations>
* </person_name>
* </contributors>
* <titles>
* <title>A Bibliography for After Jews and Arabs</title>
* </titles>
* ...
*/
$metadata = $client->work(
'doideposit::crossref',
'e0f748b2-984f-45cc-8b9e-13989c31dda4'
);
```

#### Exceptions
Expand All @@ -192,14 +238,13 @@ The constructor of both Clients can receive an optional array to add custom [Guz
$client = new Client([
'allow_redirects' => false,
'connect_timeout' => 3.14,
'timeout' => 3.14
'timeout' => 3.14,
'proxy' => [
'http' => 'http://localhost:8125', // Use this proxy with "http"
'https' => 'http://localhost:9124', // Use this proxy with "https",
'no' => ['.mit.edu', 'foo.com'] // Don't use a proxy with these
'http' => 'http://localhost:8125',
'https' => 'http://localhost:9124',
'no' => ['.mit.edu', 'foo.com'],
],
'debug' => true
...
'debug' => true,
]);
```

Expand Down
12 changes: 10 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@
},
"require-dev": {
"phpunit/phpunit": "^9.6",
"friendsofphp/php-cs-fixer": "^3.64"
"friendsofphp/php-cs-fixer": "^3.64",
"phpstan/phpstan": "^2.2"
},
"config": {
"platform": {
"php": "7.4.33"
}
},
"license": "Apache-2.0",
"autoload": {
Expand All @@ -23,6 +29,8 @@
},
"scripts" : {
"test": "vendor/bin/phpunit --colors --testdox tests/",
"lint": "vendor/bin/php-cs-fixer fix -v --diff --allow-risky=yes --config=.php-cs-fixer.php"
"analyse": "vendor/bin/phpstan analyse --debug --memory-limit=1G",
"lint": "vendor/bin/php-cs-fixer fix -v --diff --allow-risky=yes --config=.php-cs-fixer.php",
"generate-graphql-client": "php tools/generate-graphql-client.php"
}
}
Loading
Loading