diff --git a/src/Api/Field/AclConfiguration.php b/src/Api/Field/AclConfiguration.php index 42b743e..3f5e462 100644 --- a/src/Api/Field/AclConfiguration.php +++ b/src/Api/Field/AclConfiguration.php @@ -10,24 +10,31 @@ use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\Type; +/** + * @phpstan-import-type PermissiveFieldsConfig from FieldInterface + */ abstract class AclConfiguration { - public static function build(Acl $acl, EnumType $roleType): array + /** + * Return the single field configuration, including its name. + * + * @return PermissiveFieldsConfig + */ + public static function build(Acl $acl, EnumType $roleType): iterable { - return - [ - 'name' => 'aclConfiguration', - 'type' => Type::nonNull(Type::listOf(Type::nonNull(_types()->get(AclResourceConfigurationType::class)))), - 'description' => 'User friendly configuration of the ACL', - 'args' => [ - 'roles' => Type::nonNull(Type::listOf(Type::nonNull($roleType))), - ], - 'resolve' => function ($root, array $args) use ($acl): array { - $roles = $args['roles']; - $result = $acl->show(new MultipleRoles($roles)); + yield 'aclConfiguration' => fn () => [ + 'name' => 'aclConfiguration', + 'type' => Type::nonNull(Type::listOf(Type::nonNull(_types()->get(AclResourceConfigurationType::class)))), + 'description' => 'User friendly configuration of the ACL', + 'args' => [ + 'roles' => Type::nonNull(Type::listOf(Type::nonNull($roleType))), + ], + 'resolve' => function ($root, array $args) use ($acl): array { + $roles = $args['roles']; + $result = $acl->show(new MultipleRoles($roles)); - return $result; - }, - ]; + return $result; + }, + ]; } } diff --git a/src/Api/Field/FieldInterface.php b/src/Api/Field/FieldInterface.php index fabdb28..25c4adf 100644 --- a/src/Api/Field/FieldInterface.php +++ b/src/Api/Field/FieldInterface.php @@ -4,13 +4,43 @@ namespace Ecodev\Felix\Api\Field; +use GraphQL\Language\AST\FieldDefinitionNode; +use GraphQL\Type\Definition\FieldDefinition; +use GraphQL\Type\Definition\ResolveInfo; +use Mezzio\Session\SessionInterface; + /** * Represent a single field configuration. + * + * Below, we re-define some type coming from webonyx/graphql, because our `\GraphQL\Doctrine\Types::get()` + * is too poorly typed to match the correctness of webonyx/graphql. If we are able to fix our typing one day, + * then we should remove those overrides below. + * + * @phpstan-import-type FieldType from FieldDefinition + * @phpstan-import-type ComplexityFn from FieldDefinition + * @phpstan-import-type VisibilityFn from FieldDefinition + * + * @phpstan-type PermissiveArgumentListConfig iterable + * @phpstan-type FieldResolverWithSessionInterface callable(mixed, array, SessionInterface, ResolveInfo): mixed + * @phpstan-type PermissiveUnnamedFieldDefinitionConfig array{ + * type: FieldType, + * resolve?: FieldResolverWithSessionInterface|null, + * args?: PermissiveArgumentListConfig|null, + * description?: string|null, + * visible?: VisibilityFn|bool, + * deprecationReason?: string|null, + * astNode?: FieldDefinitionNode|null, + * complexity?: ComplexityFn|null + * } + * @phpstan-type PermissiveDefinitionResolver callable(): PermissiveUnnamedFieldDefinitionConfig + * @phpstan-type PermissiveFieldsConfig iterable */ interface FieldInterface { /** * Return the single field configuration, including its name. + * + * @return PermissiveFieldsConfig */ - public static function build(): array; + public static function build(): iterable; } diff --git a/src/Utility.php b/src/Utility.php index d003129..00485cd 100644 --- a/src/Utility.php +++ b/src/Utility.php @@ -8,7 +8,7 @@ use GraphQL\Doctrine\Definition\EntityID; use ReflectionClass; -abstract class Utility +final class Utility { /** * Returns the short class name of any object, eg: Application\Model\Calendar => Calendar. @@ -124,4 +124,28 @@ public static function getCookieDomain(string $input): ?string return $cookieDomain; } + + /** + * Concatenate all given iterables into a new iterator that yields key/value pairs exactly as if we iterated through each iterable sequentially. + */ + public static function concat(iterable ...$iterables): iterable + { + foreach ($iterables as $iterable) { + foreach ($iterable as $k => $v) { + yield $k => $v; + } + } + } + + /** + * Return a new iterable with only key/value pairs that match the given keys. + */ + public static function filterByKeys(iterable $things, string ...$keyToKeep): iterable + { + foreach ($things as $k => $v) { + if (in_array($k, $keyToKeep, true)) { + yield $k => $v; + } + } + } } diff --git a/tests/UtilityTest.php b/tests/UtilityTest.php index 1fe9f11..e6fbfa3 100644 --- a/tests/UtilityTest.php +++ b/tests/UtilityTest.php @@ -4,6 +4,7 @@ namespace EcodevTests\Felix; +use ArrayIterator; use Ecodev\Felix\Model\Model; use Ecodev\Felix\Utility; use EcodevTests\Felix\Blog\Model\User; @@ -178,4 +179,27 @@ public function testGetCookieDomain(string $input, ?string $expected): void $actual = Utility::getCookieDomain($input); self::assertSame($expected, $actual); } + + public function testConcat(): void + { + $iterable = new ArrayIterator([1 => 'one', 'a' => 'overridden', 'c' => 'c']); + $actual = Utility::concat(['a' => 'a', 2 => 'two'], $iterable); + + self::assertSame([ + 'a' => 'overridden', + 2 => 'two', + 1 => 'one', + 'c' => 'c', + ], iterator_to_array($actual)); + } + + public function testFilterByKeys(): void + { + $things = new ArrayIterator(['a' => 'a', 'b' => 'b', 'c' => 'c']); + + self::assertSame(['a' => 'a'], iterator_to_array(Utility::filterByKeys($things, 'a'))); + self::assertSame(['a' => 'a', 'c' => 'c'], iterator_to_array(Utility::filterByKeys($things, 'c', 'a'))); + self::assertSame([], iterator_to_array(Utility::filterByKeys($things, 'unknown-key'))); + self::assertSame([], iterator_to_array(Utility::filterByKeys($things))); + } }