Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add NativeEnumTypeConfig to generate native PHP enums #92

Merged
merged 28 commits into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 0 additions & 15 deletions .github/settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,6 @@ branches:
dismiss_stale_reviews: true
require_code_owner_reviews: true
required_approving_review_count: 1
required_status_checks:
contexts:
- "Coding Standards"
- "Static Code Analysis"
- "Tests (php7.3, lowest)"
- "Tests (php7.3, locked)"
- "Tests (php7.3, highest)"
- "Tests (php7.3, lowest)"
- "Tests (php7.3, locked)"
- "Tests (php7.3, highest)"
- "Code Coverage"
- "Mutation Tests"
- "codecov/patch"
- "codecov/project"
strict: false
restrictions: null

labels:
Expand Down
41 changes: 40 additions & 1 deletion .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,26 @@ jobs:
static-code-analysis:
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
php-version:
- 7.4
- 8.0
- 8.1
- 8.2
- 8.3
- 8.4
dependencies:
- lowest
- highest
exclude:
# Problematic because those PHP versions allow nette/php-generator 3 which can not generate native enums
- php-version: 8.1
dependencies: lowest
- php-version: 8.2
dependencies: lowest

steps:
- uses: actions/checkout@v4

Expand All @@ -34,6 +54,14 @@ jobs:
php-version: 8.3

- uses: ramsey/composer-install@v3
with:
dependency-versions: "${{ matrix.dependencies }}"

# We require PHP 8.1 to generate native enums.
# Thus, the generated output differs in other versions, so we regenerate the code there.
# This is not ideal as it makes CodegenTest less useful, but allows us to move forward with native enums.
- if: ${{ matrix.php-version <= '8.1' }}
run: make approve

- run: vendor/bin/phpstan analyse --configuration=phpstan.neon

Expand All @@ -52,8 +80,13 @@ jobs:
- 8.4
dependencies:
- lowest
- locked
- highest
exclude:
# Problematic because those PHP versions allow nette/php-generator 3 which can not generate native enums
- php-version: 8.1
dependencies: lowest
- php-version: 8.2
dependencies: lowest

steps:
- uses: actions/checkout@v4
Expand All @@ -68,6 +101,12 @@ jobs:
with:
dependency-versions: "${{ matrix.dependencies }}"

# We require PHP 8.1 to generate native enums.
# Thus, the generated output differs in other versions, so we regenerate the code there.
# This is not ideal as it makes CodegenTest less useful, but allows us to move forward with native enums.
- if: ${{ matrix.php-version <= '8.1' }}
run: make approve

- run: vendor/bin/phpunit

examples:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Add `NativeEnumTypeConfig` to generate native PHP enums https://github.com/spawnia/sailor/pull/92
- Allow `thecodingmachine/safe` v3 as dependency
- Add `CarbonTypeConfig`

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ it: fix stan approve test test-examples ## Run the commonly used targets

.PHONY: help
help: ## Displays this list of targets with descriptions
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(firstword $(MAKEFILE_LIST)) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m%-30s\033[0m %s\n", $$1, $$2}'
@grep --extended-regexp '^[a-zA-Z0-9_-]+:.*?## .*$$' $(firstword $(MAKEFILE_LIST)) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m%-30s\033[0m %s\n", $$1, $$2}'

.PHONY: fix
fix: vendor
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,10 @@ HelloSailor::setClient(null);
Custom scalars are commonly serialized as strings, but may also use other representations.
Without knowing about the contents of the type, Sailor can not do any conversions or provide more accurate type hints, so it uses `mixed`.

Enums are only supported from PHP 8.1. Many projects simply used scalar values or an implementation
that approximates enums through some kind of value class. Sailor is not opinionated and generates
enums as a class with string constants and does no conversion - useful but not perfect.
For an improved experience, it is recommended to customize the enum generation/conversion.
Since enums are only supported from PHP 8.1 and this library still supports PHP 7.4,
it generates enums as a class with string constants and handles values as `string`.
You may leverage native PHP enums by overriding `EndpointConfig::enumTypeConfig()`
and return an instance of `Spawnia\Sailor\Type\NativeEnumTypeConfig`.

Overwrite `EndpointConfig::configureTypes()` to specialize how Sailor deals with the types within your schema.
See [examples/custom-types](examples/custom-types).
Expand Down
13 changes: 6 additions & 7 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"require": {
"php": "^7.4 || ^8",
"ext-json": "*",
"nette/php-generator": "^3.6.7 || ^4",
"nette/php-generator": "^3.6.9 || ^4.1.7",
"psr/http-client": "^1",
"symfony/console": "^5 || ^6 || ^7",
"symfony/var-exporter": "^5.3 || ^6 || ^7",
Expand All @@ -35,15 +35,17 @@
"mockery/mockery": "^1.4",
"nesbot/carbon": "^2.73 || ^3",
"nyholm/psr7": "^1.4",
"ocramius/package-versions": "^1 || ^2",
"php-http/httplug": "^2",
"php-http/mock-client": "^1.4",
"phpstan/extension-installer": "^1",
"phpstan/phpstan": "^1 || ^2",
"phpstan/phpstan": "^1.12.19 || ^2",
"phpstan/phpstan-deprecation-rules": "^1 || ^2",
"phpstan/phpstan-mockery": "^1 || ^2",
"phpstan/phpstan-phpunit": "^1 || ^2",
"phpstan/phpstan-strict-rules": "^1 || ^2",
"phpunit/phpunit": "^9.5.2 || ^10 || ^11 || ^12",
"spawnia/phpunit-assert-directory": "^2",
"phpunit/phpunit": "^9.6.22 || ^10.5.45 || ^11.5.10 || ^12.0.5",
"spawnia/phpunit-assert-directory": "^2.1",
"symfony/var-dumper": "^5.2.3 || ^6 || ^7",
"thecodingmachine/phpstan-safe-rule": "^1.1"
},
Expand Down Expand Up @@ -80,9 +82,6 @@
"php-http/discovery": false,
"phpstan/extension-installer": true
},
"platform": {
"php": "7.4.15"
},
"preferred-install": "dist",
"sort-packages": true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ class MyBenSampoEnumQuery extends \Spawnia\Sailor\Operation
/**
* @param \Spawnia\Sailor\CustomTypes\Types\BenSampoEnum|null $value
*/
public static function execute($value = 'Special default value that allows Sailor to differentiate between explicitly passing null and not passing a value at all.'): MyBenSampoEnumQuery\MyBenSampoEnumQueryResult
{
public static function execute(
$value = 'Special default value that allows Sailor to differentiate between explicitly passing null and not passing a value at all.',
): MyBenSampoEnumQuery\MyBenSampoEnumQueryResult {
return self::executeOperation(
$value,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ class MyBenSampoEnumQuery extends \Spawnia\Sailor\ObjectLike
/**
* @param \Spawnia\Sailor\CustomTypes\Types\BenSampoEnum|null $withBenSampoEnum
*/
public static function make($withBenSampoEnum = 'Special default value that allows Sailor to differentiate between explicitly passing null and not passing a value at all.'): self
{
public static function make(
$withBenSampoEnum = 'Special default value that allows Sailor to differentiate between explicitly passing null and not passing a value at all.',
): self {
$instance = new self;

$instance->__typename = 'Query';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ class MyCustomEnumQuery extends \Spawnia\Sailor\Operation
/**
* @param \Spawnia\Sailor\CustomTypes\Types\CustomEnum|null $value
*/
public static function execute($value = 'Special default value that allows Sailor to differentiate between explicitly passing null and not passing a value at all.'): MyCustomEnumQuery\MyCustomEnumQueryResult
{
public static function execute(
$value = 'Special default value that allows Sailor to differentiate between explicitly passing null and not passing a value at all.',
): MyCustomEnumQuery\MyCustomEnumQueryResult {
return self::executeOperation(
$value,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ class MyCustomEnumQuery extends \Spawnia\Sailor\ObjectLike
/**
* @param \Spawnia\Sailor\CustomTypes\Types\CustomEnum|null $withCustomEnum
*/
public static function make($withCustomEnum = 'Special default value that allows Sailor to differentiate between explicitly passing null and not passing a value at all.'): self
{
public static function make(
$withCustomEnum = 'Special default value that allows Sailor to differentiate between explicitly passing null and not passing a value at all.',
): self {
$instance = new self;

$instance->__typename = 'Query';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ class MyCustomObjectQuery extends \Spawnia\Sailor\Operation
/**
* @param \Spawnia\Sailor\CustomTypesSrc\CustomObject|null $value
*/
public static function execute($value = 'Special default value that allows Sailor to differentiate between explicitly passing null and not passing a value at all.'): MyCustomObjectQuery\MyCustomObjectQueryResult
{
public static function execute(
$value = 'Special default value that allows Sailor to differentiate between explicitly passing null and not passing a value at all.',
): MyCustomObjectQuery\MyCustomObjectQueryResult {
return self::executeOperation(
$value,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ class MyCustomObjectQuery extends \Spawnia\Sailor\ObjectLike
/**
* @param \Spawnia\Sailor\CustomTypesSrc\CustomObject|null $withCustomObject
*/
public static function make($withCustomObject = 'Special default value that allows Sailor to differentiate between explicitly passing null and not passing a value at all.'): self
{
public static function make(
$withCustomObject = 'Special default value that allows Sailor to differentiate between explicitly passing null and not passing a value at all.',
): self {
$instance = new self;

$instance->__typename = 'Query';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ class MyEnumInputQuery extends \Spawnia\Sailor\Operation
/**
* @param \Spawnia\Sailor\CustomTypes\Types\EnumInput|null $input
*/
public static function execute($input = 'Special default value that allows Sailor to differentiate between explicitly passing null and not passing a value at all.'): MyEnumInputQuery\MyEnumInputQueryResult
{
public static function execute(
$input = 'Special default value that allows Sailor to differentiate between explicitly passing null and not passing a value at all.',
): MyEnumInputQuery\MyEnumInputQueryResult {
return self::executeOperation(
$input,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ class MyEnumInputQuery extends \Spawnia\Sailor\ObjectLike
/**
* @param \Spawnia\Sailor\CustomTypes\Operations\MyEnumInputQuery\WithEnumInput\EnumObject|null $withEnumInput
*/
public static function make($withEnumInput = 'Special default value that allows Sailor to differentiate between explicitly passing null and not passing a value at all.'): self
{
public static function make(
$withEnumInput = 'Special default value that allows Sailor to differentiate between explicitly passing null and not passing a value at all.',
): self {
$instance = new self;

$instance->__typename = 'Query';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class EnumObject extends \Spawnia\Sailor\ObjectLike
*/
public static function make(
$custom = 'Special default value that allows Sailor to differentiate between explicitly passing null and not passing a value at all.',
$default = 'Special default value that allows Sailor to differentiate between explicitly passing null and not passing a value at all.'
$default = 'Special default value that allows Sailor to differentiate between explicitly passing null and not passing a value at all.',
): self {
$instance = new self;

Expand Down
47 changes: 47 additions & 0 deletions examples/custom-types/expected/Operations/MyNativeEnumQuery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php declare(strict_types=1);

namespace Spawnia\Sailor\CustomTypes\Operations;

/**
* @extends \Spawnia\Sailor\Operation<\Spawnia\Sailor\CustomTypes\Operations\MyNativeEnumQuery\MyNativeEnumQueryResult>
*/
class MyNativeEnumQuery extends \Spawnia\Sailor\Operation
{
/**
* @param \Spawnia\Sailor\CustomTypes\Types\NativeEnum|null $value
*/
public static function execute(
$value = 'Special default value that allows Sailor to differentiate between explicitly passing null and not passing a value at all.',
): MyNativeEnumQuery\MyNativeEnumQueryResult {
return self::executeOperation(
$value,
);
}

protected static function converters(): array
{
static $converters;

return $converters ??= [
['value', new \Spawnia\Sailor\Convert\NullConverter(new \Spawnia\Sailor\CustomTypes\TypeConverters\NativeEnumConverter)],
];
}

public static function document(): string
{
return /* @lang GraphQL */ 'query MyNativeEnumQuery($value: NativeEnum) {
__typename
withNativeEnum(value: $value)
}';
}

public static function endpoint(): string
{
return 'custom-types';
}

public static function config(): string
{
return \Safe\realpath(__DIR__ . '/../../sailor.php');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php declare(strict_types=1);

namespace Spawnia\Sailor\CustomTypes\Operations\MyNativeEnumQuery;

/**
* @property string $__typename
* @property \Spawnia\Sailor\CustomTypes\Types\NativeEnum|null $withNativeEnum
*/
class MyNativeEnumQuery extends \Spawnia\Sailor\ObjectLike
{
/**
* @param \Spawnia\Sailor\CustomTypes\Types\NativeEnum|null $withNativeEnum
*/
public static function make(
$withNativeEnum = 'Special default value that allows Sailor to differentiate between explicitly passing null and not passing a value at all.',
): self {
$instance = new self;

$instance->__typename = 'Query';
if ($withNativeEnum !== self::UNDEFINED) {
$instance->withNativeEnum = $withNativeEnum;
}

return $instance;
}

protected function converters(): array
{
static $converters;

return $converters ??= [
'__typename' => new \Spawnia\Sailor\Convert\NonNullConverter(new \Spawnia\Sailor\Convert\StringConverter),
'withNativeEnum' => new \Spawnia\Sailor\Convert\NullConverter(new \Spawnia\Sailor\CustomTypes\TypeConverters\NativeEnumConverter),
];
}

public static function endpoint(): string
{
return 'custom-types';
}

public static function config(): string
{
return \Safe\realpath(__DIR__ . '/../../../sailor.php');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php declare(strict_types=1);

namespace Spawnia\Sailor\CustomTypes\Operations\MyNativeEnumQuery;

class MyNativeEnumQueryErrorFreeResult extends \Spawnia\Sailor\ErrorFreeResult
{
public MyNativeEnumQuery $data;

public static function endpoint(): string
{
return 'custom-types';
}

public static function config(): string
{
return \Safe\realpath(__DIR__ . '/../../../sailor.php');
}
}
Loading