Skip to content

Commit a51dc91

Browse files
committed
Improve GraphQLServices by making it a ServiceLocator
1 parent 6c4d30f commit a51dc91

File tree

8 files changed

+130
-117
lines changed

8 files changed

+130
-117
lines changed

src/Definition/GraphQLServices.php

Lines changed: 6 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -6,67 +6,22 @@
66

77
use GraphQL\Type\Definition\ResolveInfo;
88
use GraphQL\Type\Definition\Type;
9-
use LogicException;
10-
use Overblog\GraphQLBundle\Resolver\MutationResolver;
11-
use Overblog\GraphQLBundle\Resolver\QueryResolver;
12-
use Overblog\GraphQLBundle\Resolver\TypeResolver;
139
use Overblog\GraphQLBundle\Validator\InputValidator;
10+
use Symfony\Component\DependencyInjection\ServiceLocator;
1411

1512
/**
1613
* Container for special services to be passed to all generated types.
1714
*/
18-
final class GraphQLServices
15+
final class GraphQLServices extends ServiceLocator
1916
{
20-
private array $services;
21-
private TypeResolver $types;
22-
private QueryResolver $queryResolver;
23-
private MutationResolver $mutationResolver;
24-
25-
public function __construct(
26-
TypeResolver $typeResolver,
27-
QueryResolver $queryResolver,
28-
MutationResolver $mutationResolver,
29-
array $services = []
30-
) {
31-
$this->types = $typeResolver;
32-
$this->queryResolver = $queryResolver;
33-
$this->mutationResolver = $mutationResolver;
34-
$this->services = $services;
35-
}
36-
37-
/**
38-
* @return mixed
39-
*/
40-
public function get(string $name)
41-
{
42-
if (!isset($this->services[$name])) {
43-
throw new LogicException(sprintf('GraphQL service "%s" could not be located. You should define it.', $name));
44-
}
45-
46-
return $this->services[$name];
47-
}
48-
49-
/**
50-
* Get all GraphQL services.
51-
*/
52-
public function getAll(): array
53-
{
54-
return $this->services;
55-
}
56-
57-
public function has(string $name): bool
58-
{
59-
return isset($this->services[$name]);
60-
}
61-
6217
/**
6318
* @param mixed ...$args
6419
*
6520
* @return mixed
6621
*/
6722
public function query(string $alias, ...$args)
6823
{
69-
return $this->queryResolver->resolve([$alias, $args]);
24+
return $this->get('queryResolver')->resolve([$alias, $args]);
7025
}
7126

7227
/**
@@ -76,12 +31,12 @@ public function query(string $alias, ...$args)
7631
*/
7732
public function mutation(string $alias, ...$args)
7833
{
79-
return $this->mutationResolver->resolve([$alias, $args]);
34+
return $this->get('mutationResolver')->resolve([$alias, $args]);
8035
}
8136

8237
public function getType(string $typeName): ?Type
8338
{
84-
return $this->types->resolve($typeName);
39+
return $this->get('typeResolver')->resolve($typeName);
8540
}
8641

8742
/**
@@ -92,7 +47,7 @@ public function getType(string $typeName): ?Type
9247
*/
9348
public function createInputValidator($value, ArgumentInterface $args, $context, ResolveInfo $info): InputValidator
9449
{
95-
return $this->services['input_validator_factory']->create(
50+
return $this->get('input_validator_factory')->create(
9651
new ResolverArgs($value, $args, $context, $info)
9752
);
9853
}

src/DependencyInjection/Compiler/GraphQLServicesPass.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public function process(ContainerBuilder $container): void
3232
$taggedServices = array_merge($taggedServices, $deprecatedTaggedServices);
3333
}
3434

35-
$serviceContainer = ['container' => new Reference('service_container')];
35+
$locateableServices = [];
3636
$expressionLanguageDefinition = $container->findDefinition('overblog_graphql.expression_language');
3737

3838
foreach ($taggedServices as $id => $tags) {
@@ -42,9 +42,9 @@ public function process(ContainerBuilder $container): void
4242
sprintf('Service "%s" tagged "overblog_graphql.service" should have a valid "alias" attribute.', $id)
4343
);
4444
}
45-
$serviceContainer[$attributes['alias']] = new Reference($id);
45+
$locateableServices[$attributes['alias']] = new Reference($id);
4646

47-
$isPublic = isset($attributes['public']) ? (bool) $attributes['public'] : true;
47+
$isPublic = !isset($attributes['public']) || $attributes['public'];
4848
if ($isPublic) {
4949
$expressionLanguageDefinition->addMethodCall(
5050
'addGlobalName',
@@ -56,6 +56,8 @@ public function process(ContainerBuilder $container): void
5656
}
5757
}
5858
}
59-
$container->findDefinition(GraphQLServices::class)->addArgument($serviceContainer);
59+
$locateableServices['container'] = new Reference('service_container');
60+
61+
$container->findDefinition(GraphQLServices::class)->addArgument($locateableServices);
6062
}
6163
}

src/ExpressionLanguage/ExpressionLanguage.php

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ public function addGlobalName(string $index, string $name): void
2727
$this->globalNames[$index] = $name;
2828
}
2929

30+
public function getGlobalNames(): array
31+
{
32+
return array_values($this->globalNames);
33+
}
34+
3035
/**
3136
* @param string|Expression $expression
3237
* @param array $names
@@ -50,6 +55,22 @@ public function compile($expression, $names = [])
5055
* @throws SyntaxError
5156
*/
5257
public static function expressionContainsVar(string $name, $expression): bool
58+
{
59+
foreach (static::extractExpressionVarNames($expression) as $varName) {
60+
if ($name === $varName) {
61+
return true;
62+
}
63+
}
64+
65+
return false;
66+
}
67+
68+
/**
69+
* @param string|Expression $expression - expression to search in (haystack)
70+
*
71+
* @throws SyntaxError
72+
*/
73+
public static function extractExpressionVarNames($expression): iterable
5374
{
5475
if ($expression instanceof Expression) {
5576
$expression = $expression->__toString();
@@ -60,22 +81,30 @@ public static function expressionContainsVar(string $name, $expression): bool
6081
/** @var string $expression */
6182
$stream = (new Lexer())->tokenize($expression);
6283
$current = &$stream->current;
84+
$isProperty = false;
85+
$varNames = [];
6386

6487
while (!$stream->isEOF()) {
65-
if ($name === $current->value && Token::NAME_TYPE === $current->type) {
66-
// Also check that it's not a function's name
67-
$stream->next();
68-
if ('(' !== $current->value) {
69-
$contained = true;
70-
break;
88+
if ('.' === $current->value) {
89+
$isProperty = true;
90+
} elseif (Token::NAME_TYPE === $current->type) {
91+
if (!$isProperty) {
92+
$name = $current->value;
93+
// Also check that it's not a function's name
94+
$stream->next();
95+
if ('(' !== $current->value) {
96+
$varNames[] = $name;
97+
}
98+
continue;
99+
} else {
100+
$isProperty = false;
71101
}
72-
continue;
73102
}
74103

75104
$stream->next();
76105
}
77106

78-
return $contained ?? false;
107+
return $varNames;
79108
}
80109

81110
/**

src/Resources/config/services.yaml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@ services:
1010
Overblog\GraphQLBundle\Resolver\FieldResolver: ~
1111

1212
Overblog\GraphQLBundle\Definition\GraphQLServices:
13-
arguments:
14-
- '@Overblog\GraphQLBundle\Resolver\TypeResolver'
15-
- '@Overblog\GraphQLBundle\Resolver\QueryResolver'
16-
- '@Overblog\GraphQLBundle\Resolver\MutationResolver'
13+
tags: ['container.service_locator']
1714

1815
Overblog\GraphQLBundle\Request\Executor:
1916
arguments:
@@ -48,7 +45,12 @@ services:
4845
- "%overblog_graphql_types.classes_map%"
4946

5047
Overblog\GraphQLBundle\Resolver\QueryResolver:
48+
tags:
49+
- { name: overblog_graphql.service, alias: queryResolver }
50+
5151
Overblog\GraphQLBundle\Resolver\MutationResolver:
52+
tags:
53+
- { name: overblog_graphql.service, alias: mutationResolver }
5254

5355
Overblog\GraphQLBundle\Resolver\AccessResolver:
5456
arguments:

src/Validator/Constraints/ExpressionValidator.php

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
namespace Overblog\GraphQLBundle\Validator\Constraints;
66

77
use Overblog\GraphQLBundle\Definition\GraphQLServices;
8+
use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionLanguage;
89
use Overblog\GraphQLBundle\Generator\TypeGenerator;
910
use Overblog\GraphQLBundle\Validator\ValidationNode;
10-
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
1111
use Symfony\Component\HttpKernel\Kernel;
1212
use Symfony\Component\Validator\Constraint;
1313
use Symfony\Component\Validator\Constraints\Expression;
@@ -23,10 +23,10 @@ public function __construct(ExpressionLanguage $expressionLanguage, GraphQLServi
2323
{
2424
$this->expressionLanguage = $expressionLanguage;
2525
$this->graphQLServices = $graphQLServices;
26-
if (Kernel::VERSION_ID >= 40400) { // @phpstan-ignore-line
27-
parent::__construct($expressionLanguage);
28-
} else { // @phpstan-ignore-line
29-
parent::{'__construct'}(null, $expressionLanguage);
26+
if (Kernel::VERSION_ID >= 40400) {
27+
parent::__construct($expressionLanguage); // @phpstan-ignore-line
28+
} else {
29+
parent::__construct(null, $expressionLanguage); // @phpstan-ignore-line
3030
}
3131
}
3232

@@ -47,21 +47,40 @@ public function validate($value, Constraint $constraint): void
4747

4848
$variables['this'] = $object;
4949

50-
// Make all tagged GraphQL services available in the expression constraint
51-
$variables = array_merge($variables, $this->graphQLServices->getAll());
52-
5350
if ($object instanceof ValidationNode) {
5451
$variables['parentValue'] = $object->getResolverArg('value');
5552
$variables['context'] = $object->getResolverArg('context');
5653
$variables['args'] = $object->getResolverArg('args');
5754
$variables['info'] = $object->getResolverArg('info');
5855
}
5956

57+
// Make all tagged GraphQL public services available in the expression constraint
58+
$this->addGlobalVariables($constraint->expression, $variables);
59+
6060
if (!$this->expressionLanguage->evaluate($constraint->expression, $variables)) {
6161
$this->context->buildViolation($constraint->message)
6262
->setParameter('{{ value }}', $this->formatValue($value, self::OBJECT_TO_STRING))
6363
->setCode(Expression::EXPRESSION_FAILED_ERROR)
6464
->addViolation();
6565
}
6666
}
67+
68+
/**
69+
* @param string|\Symfony\Component\ExpressionLanguage\Expression $expression
70+
*/
71+
private function addGlobalVariables($expression, array &$variables): void
72+
{
73+
$globalVariables = $this->expressionLanguage->getGlobalNames();
74+
foreach (ExpressionLanguage::extractExpressionVarNames($expression) as $extractExpressionVarName) {
75+
if (
76+
isset($variables[$extractExpressionVarName])
77+
|| !$this->graphQLServices->has($extractExpressionVarName)
78+
|| !in_array($extractExpressionVarName, $globalVariables)
79+
) {
80+
continue;
81+
}
82+
83+
$variables[$extractExpressionVarName] = $this->graphQLServices->get($extractExpressionVarName);
84+
}
85+
}
6786
}

tests/Definition/GraphQLServicesTest.php

Lines changed: 0 additions & 29 deletions
This file was deleted.

tests/ExpressionLanguage/ExpressionLanguageTest.php

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,55 @@
44

55
namespace Overblog\GraphQLBundle\Tests\ExpressionLanguage;
66

7-
use Generator;
87
use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionLanguage;
98
use PHPUnit\Framework\TestCase;
109
use Symfony\Component\ExpressionLanguage\Expression;
1110

1211
class ExpressionLanguageTest extends TestCase
1312
{
1413
/**
15-
* @test
16-
* @dataProvider expressionProvider
14+
* @dataProvider expressionContainsVarProvider
1715
*
1816
* @param Expression|string $expression
1917
*/
20-
public function expressionContainsVar($expression, bool $expectedResult): void
18+
public function testExpressionContainsVar($expression, bool $expectedResult): void
2119
{
2220
$result = ExpressionLanguage::expressionContainsVar('validator', $expression);
2321

24-
$this->assertEquals($result, $expectedResult);
22+
$this->assertSame($expectedResult, $result);
2523
}
2624

27-
public function expressionProvider(): Generator
25+
/**
26+
* @dataProvider extractExpressionVarNamesProvider
27+
*
28+
* @param Expression|string $expression
29+
*/
30+
public function testExtractExpressionVarNames($expression, array $expectedResult): void
31+
{
32+
$result = ExpressionLanguage::extractExpressionVarNames($expression);
33+
34+
$this->assertSame($expectedResult, $result);
35+
}
36+
37+
public function expressionContainsVarProvider(): iterable
2838
{
2939
yield ["@=test('default', 15.6, validator)", true];
3040
yield ["@=validator('default', 15.6)", false];
3141
yield ["validator('default', validator(), 15.6)", false];
3242
yield [new Expression("validator('default', validator(), 15.6)"), false];
3343
yield [new Expression('validator'), true];
44+
yield ['toto.validator', false];
45+
yield ['toto . validator', false];
46+
yield ['toto.test && validator', true];
47+
yield ['toto . test && validator', true];
48+
}
49+
50+
public function extractExpressionVarNamesProvider(): iterable
51+
{
52+
yield ['@=a + b - c', ['a', 'b', 'c']];
53+
yield ['f()', []];
54+
yield ['a.c + b', ['a', 'b']];
55+
yield ['(a.c) + b - d', ['a', 'b', 'd']];
56+
yield['a && b && c', ['a', 'b', 'c']];
3457
}
3558
}

0 commit comments

Comments
 (0)