From a6286f8a3bdd40fe2b17c5d8e87307659f5192dd Mon Sep 17 00:00:00 2001 From: murtukov Date: Fri, 19 Jul 2019 03:24:46 +0200 Subject: [PATCH 1/4] Update expression-language.md Rework the Expression Language documentation: * Add the **Contents** section with navigation references * Replace the table of functions with a more readable list, which can be extended later * Fix and add more examples to each function * Correct some grammar mistakes * Add new section **Backslashes in expressions** * Add links to external resources * Rework titles * Rework some texts --- docs/definitions/expression-language.md | 394 +++++++++++++++++++++--- 1 file changed, 350 insertions(+), 44 deletions(-) diff --git a/docs/definitions/expression-language.md b/docs/definitions/expression-language.md index 5064fcfa7..457bb1581 100644 --- a/docs/definitions/expression-language.md +++ b/docs/definitions/expression-language.md @@ -1,52 +1,341 @@ + Expression language =================== -All definition config entries can use expression language but it must be explicitly triggered using "@=" like prefix. - -**Functions description:** - -| Expression | Description | Usage | Alias | -| ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------| -------------------------------------------------------------------------| ----- | -| object **service**(string $id) | Get a service from the container | @=service('my_service').customMethod() | serv | -| mixed **parameter**(string $name) | Get parameter from the container | @=parameter('kernel.debug') | param | -| boolean **isTypeOf**(string $className) | Verified if `value` is instance of className | @=isTypeOf('AppBundle\\User\\User') | -| mixed **resolver**(string $alias, array $args = []) | call the method on the tagged service "overblog_graphql.resolver" with args | @=resolver('blog_by_id', [value['blogID']] | res | -| mixed **mutation**(string $alias, array $args = []) | call the method on the tagged service "overblog_graphql.mutation" with args | @=mutation('remove_post_from_community', [value]) | mut | -| mixed **arguments**(array $mapping, mixed $data) | Transform and validate a list of arguments. [Arguments Transformer](../annotations/arguments-transformer.md) | @=arguments(['input' => 'MyInput'], ['input' => ['field1' => "value1"]]) | -| string **globalId**(string\|int id, string $typeName = null) | Relay node globalId | @=globalId(15, 'User') | -| array **fromGlobalId**(string $globalId) | Relay node fromGlobalId | @=fromGlobalId('QmxvZzox') | -| object **newObject**(string $className, array $args = []) | Instantiation $className object with $args | @=newObject('AppBundle\\User\\User', ['John', 15]) | -| mixed **call**(mixed $target, array $args = []) | Call the target method with given $args | @=call(service('my_service').method, ["arg1", 2]) | -| boolean **hasRole**(string $role) | Checks whether the token has a certain role. | @=hasRole('ROLE_API') | -| boolean **hasAnyRole**(string $role1, string $role2, ...string $roleN) | Checks whether the token has any of the given roles. | @=hasAnyRole('ROLE_API', 'ROLE_ADMIN') | -| boolean **isAnonymous**() | Checks whether the token is anonymous. | @=isAnonymous() | -| boolean **isRememberMe**() | Checks whether the token is remember me. | @=isRememberMe() | -| boolean **isFullyAuthenticated**() | Checks whether the token is fully authenticated. | @=isFullyAuthenticated() | -| boolean **isAuthenticated**() | Checks whether the token is not anonymous. | @=isAuthenticated() | -| boolean **hasPermission**(mixed $var, string $permission) | Checks whether the token has the given permission for the given object (requires the ACL system). | @=hasPermission(object, 'OWNER') | -| boolean **hasAnyPermission**(mixed $var, array $permissions) | Checks whether the token has any of the given permissions for the given object | @=hasAnyPermission(object, ['OWNER', 'ADMIN']) | -| User **getUser**() | Returns the user which is currently in the security token storage. User can be null. | @=getUser() | - - -**Variables description:** - -| Expression | Description | Scope | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | -| **typeResolver** | the type resolver | global | -| **object** | Refers to the value of the field for which access is being requested. For array `object` will be each item of the array. For Relay connection `object` will be the node of each connection edges. | only available for `config.fields.*.access` with query operation or mutation payload type. | -| **value** | Resolver value | only available in resolve context | -| **args** | Resolver args array | only available in resolve context | -| **info** | Resolver GraphQL\Type\Definition\ResolveInfo Object | only available in resolve context | -| **context** | context is defined by your application on the top level of query execution (useful for storing current user, environment details, etc) | only available in resolve context | -| **childrenComplexity** | Selection field children complexity | only available in complexity context | - -[For more details on expression syntax](http://symfony.com/doc/current/components/expression_language/syntax.html) +All definition config entries can use expression language but it must be explicitly triggered using the `@=` prefix. This bundle provides a set of registered functions and variables. For more details on expression syntax see the [official documentation](http://symfony.com/doc/current/components/expression_language/syntax.html). + +## Contents +- [Registered functions](#registered-functions): + - [service](#service) + - [parameter](#parameter) + - [isTypeOf](#istypeof) + - [resolver](#resolver) + - [mutation](#mutation) + - [arguments](#arguments) + - [globalId](#globalid) + - [fromGlobalId](#fromglobalid) + - [newObject](#newobject) + - [call](#call) + - [hasRole](#hasrole) + - [hasAnyRole](#hasanyrole) + - [isAnonymous](#isanonymous) + - [isRememberMe](#isrememberme) + - [isFullyAuthenticated](#isfullyauthenticated) + - [isAuthenticated](#isauthenticated) + - [hasPermission](#haspermission) + - [hasAnyPermission](#hasanypermission) + - [getUser](#getuser) +- [Registered variables](#registered-variables) +- [Private services](#private-services) +- [Custom expression functions](#custom-expression-functions) +- [Backslashes in expressions](#backslashes-in-expressions) + + +## Registered functions + +### service +**Signature**: service(string $id): object|null | **Alias**: `ser` + +Gets a service from the [service container](https://symfony.com/doc/current/service_container.html). Private services should be explicitly tagged to be accessible, see [this](#private-services) section for more details. + +Examples: +```yaml +@=service('my_service').customMethod() + +# Using the 'ser' alias +@=ser('my_service').customMethod() + +# Using the FQCN for the service name (only works for public services). +# Note the double quotes. +@=ser("App\\Manager\\UserManager").someMethod() + +# If using single quotes, you must use 4 slashes +@=ser('App\\\\Manager\\\\UserManager').someMethod() +``` + +--- + +### parameter +**Signature**: parameter(string $name): mixed | **Alias**: `param` + +Gets a parameter from the [service container](https://symfony.com/doc/current/service_container.html). + +Examples: +```yaml +@=parameter('kernel.debug') + +# Using the 'param' alias +@=param('mailer.transport') +``` + +--- + +### isTypeOf +**Signature**: isTypeOf(string $className): boolean + +Checks if the [`value`](#registered-variables) is instance of the given class name. + +Example: +```yaml +@=isTypeOf("App\\User\\User") +``` +--- + +### resolver +**Signature**: resolver(string $alias, array $args = []): mixed | **Alias**: `res` + +Calls a method on the tagged service `overblog_graphql.resolver` with `$args` + +Examples: +```yaml +# Using aliased resolver name +@=resolver('blog_by_id', [value['blogID']]) + +# Using the 'res' alias and a FQCN::methodName. +# Note the double quotes. +@=res("App\\GraphQL\\Resolver\\UserResolver::findOne", [args, info, context, value]) + +# If using single quotes, you must use 4 slashes +@=res('App\\\\GraphQL\\\\Resolver\\\\UserResolver::findOne', [args, info, context, value]) +``` +--- + +### mutation +**Signature**: mutation(string $alias, array $args = []): mixed | **Alias**: `mut` + +Calls a method on the tagged service `overblog_graphql.mutation` passing `$args` as arguments. + +Examples: +```yaml +# Using aliased mutation name +@=mutation('remove_post_from_community', [args['postId']]) + +# Using the 'mut' alias and a FQCN::methodName +# Note the double quotes. +@=mut("App\\GraphQL\\Mutation\\PostMutation::findAll", [args]) + +# If using single quotes, you must use 4 slashes +@=mut('App\\\\GraphQL\\\\Mutation\\\\PostMutation::findAll', [args]) +``` + +--- + +### arguments +**Signature**: arguments(array $mapping, mixed $data): mixed + +Transforms and validates a list of arguments. See the [Arguments Transformer](https://github.com/overblog/GraphQLBundle/blob/master/docs/annotations/arguments-transformer.md) section for more details. + +Example: +```yaml +@=arguments(['input' => 'MyInput'], ['input' => ['field1' => 'value1']]) +``` + +--- + +### globalId +**Signature**: globalId(string|int $id, string $typeName = null): string + +Relay node globalId. + +Example: +```yaml +@=globalId(15, 'User') +``` + +--- + +### fromGlobalId +**Signature**: fromGlobalId(string $globalId): array + +Relay node globalId. + +Example: +```yaml +@=fromGlobalId(‘QmxvZzox’) +``` + +--- + +### newObject +**Signature**: newObject(string $className, array $args = []): object + +Creates a new class instance from given class name and arguments. Uses the following php code under the hood: +```php +(new ReflectionClass($className))->newInstanceArgs($args) +``` +See the [official documentation](https://www.php.net/manual/en/reflectionclass.newinstanceargs.php) for more details about the `ReflectionClass::newInstanceArgs` method. + +Examples: +```yaml +@=newObject("App\\Entity\\User", ["John", 15]) + +# Using inside another function (resolver) +@=resolver("myResolver", [newObject("App\\User\\User", [args])]) +``` + +--- + +### call +**Signature**: call(callable $target, array $args = []): mixed + +Calls a function or a static method, passing `$args` to it as arguments. + +Examples: +```yaml +# Calling a static method using a FCN string +@=call("App\\Util\\Validator::email", ["arg1", 2]) + +# Calling a static method using an array callable +call(["App\\Util\\Validator", "email"], [args["email"]]) + +# Calling a function +@=call('array_merge', [args['array1'], args['array2']]) +``` + +--- + +### hasRole +**Signature**: hasRole(string $role): bool + +Checks whether the logged in user has a certain role. + +Example: +```yaml +@=hasRole('ROLE_API') +``` + +--- + +### hasAnyRole +**Signature**: hasAnyRole(string $role1, string $role2, .\.\.string $roleN): bool + +Checks whether the logged in user has at least one of the given roles. + +Example: +```yaml +@=hasAnyRole('ROLE_API', 'ROLE_ADMIN') +``` + +--- + +### isAnonymous +**Signature**: isAnonymous(): bool + +Checks whether the token is anonymous. Shorthand for: +```php +AuthorizationChecker::isGranted('IS_AUTHENTICATED_ANONYMOUSLY') +``` + + +Example: +```yaml +@=isAnonymous() +``` + +--- + +### isRememberMe +**Signature**: isRememberMe(): bool + +Checks whether the token is remembered. Shorthand for : +```php +AuthorizationChecker::isGranted('IS_AUTHENTICATED_REMEMBERED') +``` + +Example: +```yaml +@=isRememberMe() +``` + +--- + +### isFullyAuthenticated +**Signature**: isFullyAuthenticated(): bool + +Checks whether the token is fully authenticated. Shorthand for: +```php +AuthorizationChecker::isGranted('IS_AUTHENTICATED_FULLY') +``` + +Example: +```yaml +@=isFullyAuthenticated() +``` + +--- + +### isAuthenticated() +**Signature**: isAuthenticated(): bool + +Checks whether the token is not anonymous. Shorthand for: +```php +AuthorizationChecker::isGranted('IS_AUTHENTICATED_REMEMBERED') || AuthorizationChecker::isGranted('IS_AUTHENTICATED_FULLY') +``` + +Example: +```yaml +@=isAuthenticated() +``` + +--- + +### hasPermission +**Signature**: hasPermission(object $object, string $permission): bool + +Checks whether logged in user has given permission for given object (requires [symfony/acl-bundle](https://github.com/symfony/acl-bundle) to be installed). + +Example: +```yaml +# Using in combination with the 'service' function. +@=hasPermission(ser('user_repository').find(1), ‘OWNER’) +``` + +--- + +### hasAnyPermission +**Signature**: hasAnyPermission(object $object, array $permission): bool + +Checks whether the token has any of the given permissions for the given object + +Example: +```yaml +# Using in combination with the 'service' function +@=hasAnyPermission(service('my_service').getObject(), [‘OWNER’, ‘ADMIN’]) +``` + +--- + +### getUser +**Signature**: getUser(): Symfony\Component\Security\Core\User\UserInterface|null + +Returns the user which is currently in the security token storage. + +Examples +```yaml +@=getUser() + +# Checking if user has particular name +@=getUser().firstName === 'adam' +``` + +## Registered variables: + +| Variable | Description | Scope| +|:-------------------- |:------------ |:---- | +| `typeResolver` | An object of class `Overblog\GraphQLBundle\Resolver\TypeResolver`| global| +| `object` | Refers to the value of the field for which access is being requested. For array `object` will be each item of the array. For Relay connection `object` will be the node of each connection edges. | only available for `config.fields.*.access` with query operation or mutation payload type. | +| `value` | The value returned by a previous resolver | only available in `resolve` context| +| `args` | An array of argument values of current resolver | only available in `resolve` context | +| `info` | A `GraphQL\Type\Definition\ResolveInfo` object of current resolver | only available in `resolve` context| +| `context` | context is defined by your application on the top level of query execution (useful for storing current user, environment details, etc)| only available in `resolve` context | +| `childrenComplexity` | Selection field children complexity | only available in `complexity` context| + Private services ---------------- -It is not possible to use private services with `service` or `serv` functions since this is equivalent to call -`get` method on the container. Private services must be tag as global variable to be accessible. +It is not possible to use private services with [`service`](#service) functions since this is equivalent to call +`get` method on the container. In order to make private services accessible, they must be tagged with `overblog_graphql.global_variable`. Yaml example: @@ -78,7 +367,7 @@ MyType: resolve: '@=my_private_service.formatName(value)' ``` -Custom expression function +Custom expression functions -------------------------- Adding custom expression function is easy since all you need to do is create a tagged service. @@ -108,7 +397,7 @@ class JsonDecode extends ExpressionFunction } ``` -now register your service +now register your service: ```yaml App\ExpressionLanguage\JsonDecode: @@ -129,3 +418,20 @@ Object: **Tips**: At last if this is not an answer to all your needs, the expression language service can be customized using bundle configuration. + +## Backslashes in expressions + +Backslashes in expressions must be escaped by 2 or 4 backslasehs, depending on which quotes do you use. + +When using **single quotes** as _outer_ quotes, you must use **double backslashes**. e.g.: +```yaml +... +access: '@=resolver("App\\GraphQL\\Resolver\\ResolverName::methodName")' +... +``` +When using **double quotes** as _outer_ quotes, you must use **4 backslashes**, e.g.: +```yaml +... +access: "@=resolver('App\\\\GraphQL\\\\Resolver\\\\ResolverName::methodName')" +... +``` From a61bc80c0b96d02e00c920056f509d21065a02fa Mon Sep 17 00:00:00 2001 From: murtukov Date: Fri, 19 Jul 2019 03:34:49 +0200 Subject: [PATCH 2/4] Update expression-language.md --- docs/definitions/expression-language.md | 34 ------------------------- 1 file changed, 34 deletions(-) diff --git a/docs/definitions/expression-language.md b/docs/definitions/expression-language.md index 457bb1581..1000d58b8 100644 --- a/docs/definitions/expression-language.md +++ b/docs/definitions/expression-language.md @@ -53,8 +53,6 @@ Examples: @=ser('App\\\\Manager\\\\UserManager').someMethod() ``` ---- - ### parameter **Signature**: parameter(string $name): mixed | **Alias**: `param` @@ -68,8 +66,6 @@ Examples: @=param('mailer.transport') ``` ---- - ### isTypeOf **Signature**: isTypeOf(string $className): boolean @@ -79,7 +75,6 @@ Example: ```yaml @=isTypeOf("App\\User\\User") ``` ---- ### resolver **Signature**: resolver(string $alias, array $args = []): mixed | **Alias**: `res` @@ -98,7 +93,6 @@ Examples: # If using single quotes, you must use 4 slashes @=res('App\\\\GraphQL\\\\Resolver\\\\UserResolver::findOne', [args, info, context, value]) ``` ---- ### mutation **Signature**: mutation(string $alias, array $args = []): mixed | **Alias**: `mut` @@ -118,8 +112,6 @@ Examples: @=mut('App\\\\GraphQL\\\\Mutation\\\\PostMutation::findAll', [args]) ``` ---- - ### arguments **Signature**: arguments(array $mapping, mixed $data): mixed @@ -130,8 +122,6 @@ Example: @=arguments(['input' => 'MyInput'], ['input' => ['field1' => 'value1']]) ``` ---- - ### globalId **Signature**: globalId(string|int $id, string $typeName = null): string @@ -142,8 +132,6 @@ Example: @=globalId(15, 'User') ``` ---- - ### fromGlobalId **Signature**: fromGlobalId(string $globalId): array @@ -154,8 +142,6 @@ Example: @=fromGlobalId(‘QmxvZzox’) ``` ---- - ### newObject **Signature**: newObject(string $className, array $args = []): object @@ -173,8 +159,6 @@ Examples: @=resolver("myResolver", [newObject("App\\User\\User", [args])]) ``` ---- - ### call **Signature**: call(callable $target, array $args = []): mixed @@ -192,8 +176,6 @@ call(["App\\Util\\Validator", "email"], [args["email"]]) @=call('array_merge', [args['array1'], args['array2']]) ``` ---- - ### hasRole **Signature**: hasRole(string $role): bool @@ -204,8 +186,6 @@ Example: @=hasRole('ROLE_API') ``` ---- - ### hasAnyRole **Signature**: hasAnyRole(string $role1, string $role2, .\.\.string $roleN): bool @@ -216,8 +196,6 @@ Example: @=hasAnyRole('ROLE_API', 'ROLE_ADMIN') ``` ---- - ### isAnonymous **Signature**: isAnonymous(): bool @@ -232,8 +210,6 @@ Example: @=isAnonymous() ``` ---- - ### isRememberMe **Signature**: isRememberMe(): bool @@ -247,8 +223,6 @@ Example: @=isRememberMe() ``` ---- - ### isFullyAuthenticated **Signature**: isFullyAuthenticated(): bool @@ -262,8 +236,6 @@ Example: @=isFullyAuthenticated() ``` ---- - ### isAuthenticated() **Signature**: isAuthenticated(): bool @@ -277,8 +249,6 @@ Example: @=isAuthenticated() ``` ---- - ### hasPermission **Signature**: hasPermission(object $object, string $permission): bool @@ -290,8 +260,6 @@ Example: @=hasPermission(ser('user_repository').find(1), ‘OWNER’) ``` ---- - ### hasAnyPermission **Signature**: hasAnyPermission(object $object, array $permission): bool @@ -303,8 +271,6 @@ Example: @=hasAnyPermission(service('my_service').getObject(), [‘OWNER’, ‘ADMIN’]) ``` ---- - ### getUser **Signature**: getUser(): Symfony\Component\Security\Core\User\UserInterface|null From 879f5a520bf890b2e4fc575af2a9762602521a0c Mon Sep 17 00:00:00 2001 From: murtukov Date: Fri, 19 Jul 2019 03:42:48 +0200 Subject: [PATCH 3/4] Update expression-language.md Add horizontal dividers --- docs/definitions/expression-language.md | 36 +++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/docs/definitions/expression-language.md b/docs/definitions/expression-language.md index 1000d58b8..2816f2fa3 100644 --- a/docs/definitions/expression-language.md +++ b/docs/definitions/expression-language.md @@ -53,6 +53,8 @@ Examples: @=ser('App\\\\Manager\\\\UserManager').someMethod() ``` +--- + ### parameter **Signature**: parameter(string $name): mixed | **Alias**: `param` @@ -66,6 +68,8 @@ Examples: @=param('mailer.transport') ``` +--- + ### isTypeOf **Signature**: isTypeOf(string $className): boolean @@ -76,6 +80,8 @@ Example: @=isTypeOf("App\\User\\User") ``` +--- + ### resolver **Signature**: resolver(string $alias, array $args = []): mixed | **Alias**: `res` @@ -94,6 +100,8 @@ Examples: @=res('App\\\\GraphQL\\\\Resolver\\\\UserResolver::findOne', [args, info, context, value]) ``` +--- + ### mutation **Signature**: mutation(string $alias, array $args = []): mixed | **Alias**: `mut` @@ -112,6 +120,8 @@ Examples: @=mut('App\\\\GraphQL\\\\Mutation\\\\PostMutation::findAll', [args]) ``` +--- + ### arguments **Signature**: arguments(array $mapping, mixed $data): mixed @@ -122,6 +132,8 @@ Example: @=arguments(['input' => 'MyInput'], ['input' => ['field1' => 'value1']]) ``` +--- + ### globalId **Signature**: globalId(string|int $id, string $typeName = null): string @@ -132,6 +144,8 @@ Example: @=globalId(15, 'User') ``` +--- + ### fromGlobalId **Signature**: fromGlobalId(string $globalId): array @@ -142,6 +156,8 @@ Example: @=fromGlobalId(‘QmxvZzox’) ``` +--- + ### newObject **Signature**: newObject(string $className, array $args = []): object @@ -159,6 +175,8 @@ Examples: @=resolver("myResolver", [newObject("App\\User\\User", [args])]) ``` +--- + ### call **Signature**: call(callable $target, array $args = []): mixed @@ -176,6 +194,8 @@ call(["App\\Util\\Validator", "email"], [args["email"]]) @=call('array_merge', [args['array1'], args['array2']]) ``` +--- + ### hasRole **Signature**: hasRole(string $role): bool @@ -186,6 +206,8 @@ Example: @=hasRole('ROLE_API') ``` +--- + ### hasAnyRole **Signature**: hasAnyRole(string $role1, string $role2, .\.\.string $roleN): bool @@ -196,6 +218,8 @@ Example: @=hasAnyRole('ROLE_API', 'ROLE_ADMIN') ``` +--- + ### isAnonymous **Signature**: isAnonymous(): bool @@ -210,6 +234,8 @@ Example: @=isAnonymous() ``` +--- + ### isRememberMe **Signature**: isRememberMe(): bool @@ -223,6 +249,8 @@ Example: @=isRememberMe() ``` +--- + ### isFullyAuthenticated **Signature**: isFullyAuthenticated(): bool @@ -236,6 +264,8 @@ Example: @=isFullyAuthenticated() ``` +--- + ### isAuthenticated() **Signature**: isAuthenticated(): bool @@ -249,6 +279,8 @@ Example: @=isAuthenticated() ``` +--- + ### hasPermission **Signature**: hasPermission(object $object, string $permission): bool @@ -260,6 +292,8 @@ Example: @=hasPermission(ser('user_repository').find(1), ‘OWNER’) ``` +--- + ### hasAnyPermission **Signature**: hasAnyPermission(object $object, array $permission): bool @@ -271,6 +305,8 @@ Example: @=hasAnyPermission(service('my_service').getObject(), [‘OWNER’, ‘ADMIN’]) ``` +--- + ### getUser **Signature**: getUser(): Symfony\Component\Security\Core\User\UserInterface|null From 8fb9d6fa441c67532e1e6e69e90786f0218c3048 Mon Sep 17 00:00:00 2001 From: murtukov Date: Fri, 19 Jul 2019 03:46:46 +0200 Subject: [PATCH 4/4] Update expression-language.md Fix quotes --- docs/definitions/expression-language.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/definitions/expression-language.md b/docs/definitions/expression-language.md index 2816f2fa3..338b1e6d0 100644 --- a/docs/definitions/expression-language.md +++ b/docs/definitions/expression-language.md @@ -289,7 +289,7 @@ Checks whether logged in user has given permission for given object (requires [s Example: ```yaml # Using in combination with the 'service' function. -@=hasPermission(ser('user_repository').find(1), ‘OWNER’) +@=hasPermission(ser('user_repository').find(1), 'OWNER') ``` --- @@ -302,7 +302,7 @@ Checks whether the token has any of the given permissions for the given object Example: ```yaml # Using in combination with the 'service' function -@=hasAnyPermission(service('my_service').getObject(), [‘OWNER’, ‘ADMIN’]) +@=hasAnyPermission(service('my_service').getObject(), ['OWNER', 'ADMIN']) ``` ---