Skip to content
Open
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

## v1.3.0

### Fixed

- Handle fields omitted through `@skip` or `@include` https://github.com/spawnia/sailor/pull/79

## v1.2.1

### Fixed
Expand Down
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,31 @@ the following is more efficient as it does not instantiate a new object:
->assertErrorFree(); // Throws if there are errors
```

### Client directives

When using GraphQL's `@skip` or `@include` directives in your operations, fields can be omitted from the server response.
Sailor marks such fields as nullable in the generated result classes, allowing you to safely handle cases where they are absent:

```graphql
query UserProfile($skipEmail: Boolean!) {
user {
name
email @skip(if: $skipEmail)
}
}
```

The generated `email` field will be nullable, even if it was non-nullable in the schema.
When skipped (or not included), the property will always be `null`, but can be accessed without error.

```php
UserProfile::execute(skipEmail: true)
->errorFree()
->data
->user
->email // null
```

### Queries with arguments

Your generated operation classes will be annotated with the arguments your query defines.
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"psr-4": {
"Spawnia\\Sailor\\CustomTypesSrc\\": "examples/custom-types/src/",
"Spawnia\\Sailor\\CustomTypes\\": "examples/custom-types/expected/",
"Spawnia\\Sailor\\InlineFragments\\": "examples/inline-fragments/expected/",
"Spawnia\\Sailor\\Input\\": "examples/input/expected/",
"Spawnia\\Sailor\\PhpKeywords\\": "examples/php-keywords/expected/",
"Spawnia\\Sailor\\Polymorphic\\": "examples/polymorphic/expected/",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php declare(strict_types=1);

namespace Spawnia\Sailor\InlineFragments\Operations;

/**
* @extends \Spawnia\Sailor\Operation<\Spawnia\Sailor\InlineFragments\Operations\InlineFragmentWithDirectNonNullableField\InlineFragmentWithDirectNonNullableFieldResult>
*/
class InlineFragmentWithDirectNonNullableField extends \Spawnia\Sailor\Operation
{
/**
* @param bool $skip
*/
public static function execute(
$skip,
): InlineFragmentWithDirectNonNullableField\InlineFragmentWithDirectNonNullableFieldResult {
return self::executeOperation(
$skip,
);
}

protected static function converters(): array
{
/** @var array<int, array{string, \Spawnia\Sailor\Convert\TypeConverter}>|null $converters */
static $converters;

return $converters ??= [
['skip', new \Spawnia\Sailor\Convert\NonNullConverter(new \Spawnia\Sailor\Convert\BooleanConverter)],
];
}

public static function document(): string
{
return /* @lang GraphQL */ 'query InlineFragmentWithDirectNonNullableField($skip: Boolean!) {
__typename
search(query: "test") {
__typename
... on Article @skip(if: $skip) {
title
}
}
}';
}

public static function endpoint(): string
{
return 'inline-fragments';
}

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

namespace Spawnia\Sailor\InlineFragments\Operations\InlineFragmentWithDirectNonNullableField;

/**
* @property array<int, \Spawnia\Sailor\InlineFragments\Operations\InlineFragmentWithDirectNonNullableField\Search\Article|\Spawnia\Sailor\InlineFragments\Operations\InlineFragmentWithDirectNonNullableField\Search\Video> $search
* @property string $__typename
*/
class InlineFragmentWithDirectNonNullableField extends \Spawnia\Sailor\ObjectLike
{
/**
* @param array<int, \Spawnia\Sailor\InlineFragments\Operations\InlineFragmentWithDirectNonNullableField\Search\Article|\Spawnia\Sailor\InlineFragments\Operations\InlineFragmentWithDirectNonNullableField\Search\Video> $search
*/
public static function make($search): self
{
$instance = new self;

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

return $instance;
}

protected function converters(): array
{
/** @var array<string, \Spawnia\Sailor\Convert\TypeConverter>|null $converters */
static $converters;

return $converters ??= [
'search' => new \Spawnia\Sailor\Convert\NonNullConverter(new \Spawnia\Sailor\Convert\ListConverter(new \Spawnia\Sailor\Convert\NonNullConverter(new \Spawnia\Sailor\Convert\PolymorphicConverter([
'Article' => '\\Spawnia\\Sailor\\InlineFragments\\Operations\\InlineFragmentWithDirectNonNullableField\\Search\\Article',
'Video' => '\\Spawnia\\Sailor\\InlineFragments\\Operations\\InlineFragmentWithDirectNonNullableField\\Search\\Video',
])))),
'__typename' => new \Spawnia\Sailor\Convert\NonNullConverter(new \Spawnia\Sailor\Convert\StringConverter),
];
}

public static function endpoint(): string
{
return 'inline-fragments';
}

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\InlineFragments\Operations\InlineFragmentWithDirectNonNullableField;

class InlineFragmentWithDirectNonNullableFieldErrorFreeResult extends \Spawnia\Sailor\ErrorFreeResult
{
public InlineFragmentWithDirectNonNullableField $data;

public static function endpoint(): string
{
return 'inline-fragments';
}

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

namespace Spawnia\Sailor\InlineFragments\Operations\InlineFragmentWithDirectNonNullableField;

class InlineFragmentWithDirectNonNullableFieldResult extends \Spawnia\Sailor\Result
{
public ?InlineFragmentWithDirectNonNullableField $data = null;

protected function setData(\stdClass $data): void
{
$this->data = InlineFragmentWithDirectNonNullableField::fromStdClass($data);
}

/**
* Useful for instantiation of successful mocked results.
*
* @return static
*/
public static function fromData(InlineFragmentWithDirectNonNullableField $data): self
{
$instance = new static;
$instance->data = $data;

return $instance;
}

public function errorFree(): InlineFragmentWithDirectNonNullableFieldErrorFreeResult
{
return InlineFragmentWithDirectNonNullableFieldErrorFreeResult::fromResult($this);
}

public static function endpoint(): string
{
return 'inline-fragments';
}

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

namespace Spawnia\Sailor\InlineFragments\Operations\InlineFragmentWithDirectNonNullableField\Search;

/**
* @property string $__typename
* @property string|null $title
*/
class Article extends \Spawnia\Sailor\ObjectLike
{
/**
* @param string|null $title
*/
public static function make(
$title = '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 = 'Article';
if ($title !== self::UNDEFINED) {
$instance->__set('title', $title);
}

return $instance;
}

protected function converters(): array
{
/** @var array<string, \Spawnia\Sailor\Convert\TypeConverter>|null $converters */
static $converters;

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

public static function endpoint(): string
{
return 'inline-fragments';
}

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

namespace Spawnia\Sailor\InlineFragments\Operations\InlineFragmentWithDirectNonNullableField\Search;

/**
* @property string $__typename
*/
class Video extends \Spawnia\Sailor\ObjectLike
{
public static function make(): self
{
$instance = new self;

$instance->__typename = 'Video';

return $instance;
}

protected function converters(): array
{
/** @var array<string, \Spawnia\Sailor\Convert\TypeConverter>|null $converters */
static $converters;

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

public static function endpoint(): string
{
return 'inline-fragments';
}

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

namespace Spawnia\Sailor\InlineFragments\Operations;

/**
* @extends \Spawnia\Sailor\Operation<\Spawnia\Sailor\InlineFragments\Operations\InlineFragmentWithNestedNonNullableField\InlineFragmentWithNestedNonNullableFieldResult>
*/
class InlineFragmentWithNestedNonNullableField extends \Spawnia\Sailor\Operation
{
/**
* @param bool $skip
*/
public static function execute(
$skip,
): InlineFragmentWithNestedNonNullableField\InlineFragmentWithNestedNonNullableFieldResult {
return self::executeOperation(
$skip,
);
}

protected static function converters(): array
{
/** @var array<int, array{string, \Spawnia\Sailor\Convert\TypeConverter}>|null $converters */
static $converters;

return $converters ??= [
['skip', new \Spawnia\Sailor\Convert\NonNullConverter(new \Spawnia\Sailor\Convert\BooleanConverter)],
];
}

public static function document(): string
{
return /* @lang GraphQL */ 'query InlineFragmentWithNestedNonNullableField($skip: Boolean!) {
__typename
search(query: "test") {
__typename
... on Article @skip(if: $skip) {
content {
__typename
text
}
}
}
}';
}

public static function endpoint(): string
{
return 'inline-fragments';
}

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