Giter Club home page Giter Club logo

railt's Introduction

Railt

PHP 8.1+ railt.org Discord Latest Stable Version Total Downloads License MIT

Testing

Introduction

Project idea is clean and high-quality code.

Unlike most (all at the moment) implementations, like webonyx, youshido or digitalonline the Railt contains a completely own implementation of the GraphQL SDL parser which is based on EBNF-like grammar. This opportunity allows not only to have the original implementation of the language and to keep it always up to date, but also to implement a new backward compatible functionality that is not available to other implementations.

Goal of Railt:

  • Do not repeat the mistakes made in the JS-based implementations.
  • Implement a modern and convenient environment for PHP developers.
  • Implement easy integration into any ready-made solutions based on PSR.
  • Provide familiar functionality (including dependency injection, routing, etc.).

Installation

  • composer require railt/railt

Quick Start

This tutorial helps you:

  • Obtain a basic understanding of GraphQL principles.
  • Define a GraphQL schema that represents the structure of your data set.
  • Run an instance of Railt Application that lets you execute queries against your schema.

This tutorial assumes that you are familiar with the command line and PHP and have installed a recent PHP (v8.1+) version.

Step 1: Create a new project

  1. From your preferred development directory, create a directory for a new project and cd into it:
mkdir railt-example
cd railt-example
  1. Initialize a new project with Composer:
composer init
composer require railt/railt dev-master@dev

Your project directory now contains a composer.json file.

Please note that in case of installation errors related to installing the dev version ("The package is not available in a stable-enough version according to your minimum-stability setting"), you need to specify "minimum-stability": "dev" in composer.json file.

See more at https://getcomposer.org/doc/04-schema.md#minimum-stability

Applications that run Railt Application may require two top-level dependencies:

  • railt/webonyx-executor - An executor that provides a webonyx/graphql-php bridge for launching and processing GraphQL requests.
  • railt/router-extension - A router extension that provides a convenient way to delegate GraphQL requests to controller instances.

Alternatively, you can install all components separately:

composer require railt/factory railt/webonyx-executor railt/router-extension

Step 2: Define your GraphQL schema

Every GraphQL application (including Railt) uses a schema to define the structure of data that clients can query. In this example, we'll create an application for querying a collection of users by id and name.

Open index.graphqls in your preferred code editor and paste the following into it:

# Comments in GraphQL strings (such as this one)
# start with the hash (#) symbol.

# This "User" type defines the queryable fields for
# every user in our data source.
type User {
    id: ID
    name: String
}

# The "Query" type is special: it lists all of the
# available queries that clients can execute, along with
# the return type for each. In this case, the "books"
# query returns an array of zero or more Books (defined above).
type Query {
    users: [User]
}

Now just open (create) the index.php file and paste the following into it:

<?php

require __DIR__ . '/vendor/autoload.php';

//
// Create An Application
//
$application = new Railt\Foundation\Application(
    executor: new Railt\Executor\Webonyx\WebonyxExecutor(),
);

$application->extend(new Railt\Extension\Router\DispatcherExtension());

//
// Creating a connection instance that will process
// incoming requests and return responses.
//
$connection = $application->connect(
    schema: new \SplFileInfo(__DIR__ . '/index.graphqls'),
);

This snippet defines a simple, valid GraphQL schema. Clients will be able to execute a query named users, and our server will return an array of zero or more Users.

Step 2.1: Schema health check

To health check an application, you can create a GraphQLRequest instance manually by passing the request object with the desired GraphQL query string.

//
// Passing a request to the specified connection.
// 
$response = $connection->handle(
    request: new \Railt\Http\GraphQLRequest(
        query: '{ users { id, name } }',
    ),
);

dump($response->toArray());

//
// Expected Output:
//
// array:1 [
//   "data" => array:1 [
//     "users" => []
//   ]
// ]
//

Step 3: Define controller

Resolvers tell Railt Application how to fetch the data associated with a particular type. Because our User array is hardcoded, the corresponding resolver is straightforward.

Create a controller file with a UserController class, for example with a index() method and the following code:

<?php

class UserController
{
    public function index(): iterable
    {
        return [
            ['id' => 1, 'name' => 'Vasya'],
            ['id' => 2, 'name' => 'Petya'],
        ];
    }
}

Make sure that this class is available for autoloading or the file is included in the index.php.

Step 4: Bind field to controller action

We've defined our data set, but Railt application doesn't know that it should use that data set when it's executing a query. To fix this, we create a route.

Route tell Railt how to fetch the data associated with a particular type. Because our User array is hardcoded, the corresponding route is straightforward.

Add the following @route directive to the bottom of your index.graphqls file:

# ...
type User {
    id: ID
    name: String
}

# ...
type Query {
    users: [User]
        # Route directive can be defined here
        @route(action: "UserController->index")
}

Step 5: Working with HTTP

To pass the request data and send the response, we must complete our index.php file.

{tip} In the case that you use Symfony, Laravel or another http layer (for example, psr-7), then you can organize data acquisition according to the provided framework API and/or specification.

$data = json_decode(file_get_contents('php://input'), true);

$response = $connection->handle(
    request: new \Railt\Http\GraphQLRequest(
        query: $data['query'] ?? '',
        variables: $data['variables'] ?? [],
        operationName: $data['operationName'] ?? null,
    ),
);

$json = json_encode($response->toArray());

header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');

echo $json;

Step 6: Start the server

We're ready to start our server! Run the following command from your project's root directory:

php -S 127.0.0.0:80

You should now see the following output at the bottom of your terminal:

PHP 8.2.6 Development Server (http://127.0.0.1:80) started

We're up and running!

Step 7: Execute your first query

We can now execute GraphQL queries on our server. To execute our first query, we can use Apollo Sandbox, GraphQL Playground or something else.

Our server supports a single query named users. Let's execute it!

Here's a GraphQL query string for executing the users query:

{
  users {
    id
    name
  }
}

Paste this string into the query panel and click the "send request" button (The GraphQL interface and panel layout may depend on the platform/client you are using). The results (from our hardcoded data set) appear in the response panel:

/img/get-started-request.png

One of the most important concepts of GraphQL is that clients can choose to query only for the fields they need. Delete name from the query string and execute it again. The response updates to include only the id field for each User!

Learning Railt

Full documentation can be found on the official site.

Contributing

Thank you for considering contributing to the Railt Framework! The contribution guide can be found in the documentation.

Security Vulnerabilities

If you discover a security vulnerability within Railt, please send an e-mail to maintainer at [email protected]. All security vulnerabilities will be promptly addressed.

License

The Railt Framework is open-sourced software licensed under the MIT license.

Help & Community Discord

Join our Discord community if you run into issues or have questions. We love talking to you!

Supported By

JetBrains

railt's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

railt's Issues

Queries: Fetching from "yield return"

Will be (can be) usage for eager DB relations:

/**
 * Additional fetching from "yield return"
 */
class UsersQuery extends AbstractQuery
{
    public function resolve(Query $query): iterable
    {
        $db = $this->db->table('users');

        yield 'comments' => $query->then(function() use ($db) {
            $db->leftJoin('comments', ...);
        });

        return $db->fetchAll();
    }
}

Class 'Railt\Compiler\Parser\Runtime' not found

  • Railt Version: dev-master
  • PHP Version: 7.1
  • Parser

Description:

Fatal error: Class 'Railt\Compiler\Parser\Runtime' not found in /.../vendor/railt/sdl/src/Parser/SchemaParser.php on line 18
Call Stack
#	Time	Memory	Function	Location
1	0.0023	396472	{main}( )	.../index.php:0
2	0.0313	712448	Railt\Foundation\Application->request( )	.../index.php:51
3	0.0313	712448	Railt\Foundation\Extensions\Repository->boot( )	.../Application.php:191
4	0.0313	712824	Railt\Container\Container->call( )	.../Repository.php:66
5	0.0313	712824	Railt\Container\ParamResolver->fromClosure( )	.../Container.php:199
6	0.0313	712936	Railt\Container\ParamResolver->resolve( )	.../ParamResolver.php:42
7	0.0314	713448	Railt\Container\ParamResolver->resolveParameter( )	.../ParamResolver.php:90
8	0.0314	713448	Railt\Container\ParamResolver->get( )	.../ParamResolver.php:109
9	0.0314	713448	Railt\Container\Container->make( )	.../ParamResolver.php:151
10	0.0314	713448	Railt\Container\Container->get( )	.../Container.php:215
11	0.0314	713448	Railt\Container\Container->resolve( )	.../Container.php:107
12	0.0314	713448	Railt\Container\Container->call( )	.../Container.php:181
13	0.0315	713880	Railt\Foundation\Services\CompilerService->Railt\Foundation\Services\{closure}( )	.../Container.php:201
14	0.0335	718456	Railt\SDL\Compiler->__construct( )	.../CompilerService.php:32
15	0.0349	733280	Railt\SDL\Parser\Factory->getParser( )	.../Compiler.php:86
16	0.0349	733280	Railt\SDL\Parser\Factory->createParser( )	.../Factory.php:38
17	0.0349	733280	class_exists ( )	.../Factory.php:61
18	0.0349	733336	spl_autoload_call ( )	.../Factory.php:61
19	0.0349	733392	Composer\Autoload\ClassLoader->loadClass( )	.../Factory.php:61
20	0.0349	733520	Composer\Autoload\includeFile( )	.../ClassLoader.php:322
21	0.0370	736744	include( '/__/vendor/railt/sdl/src/Parser/SchemaParser.php' )	.../ClassLoader.php:444

Steps To Reproduce:

composer init
composer require laravel/framework
composer require railt/rail

then - just try out the example snippet from here https://ru.railt.org/docs/introduction/installation

use Railt\Io\File;
use Railt\SDL\Compiler;
use Railt\Http\Request;
use Railt\Foundation\Application;
use Railt\Http\Provider\GlobalsProvider;

require __DIR__ . '/vendor/autoload.php';
$app = new Application();
$schema = File::fromPathname(__DIR__ . '/schema.graphqls');
$request = new Request(new GlobalsProvider());

// here execution brakes!
$response = $app->request($schema, $request); 
$response->send();

Expected result:

expected demo

What happens instead?

stack trace is above

Question.

maybe I should test out this framework, by requiring each component seprately? and all component version should match?

RFC 325: Any scalar type

About

The Any scalar type represents any value that is supported by underlying serialization protocol (including lists and maps). It is intended to be used as an opt-out type in cases when the exact type is not known in advance.

Note: If Any is wrapped by Non-Null then standard rules are applied. That means {null} value will produce error both for input and output values.

Example

type Something {
    say(message: Any): Any
}

Directive invocation argument value is empty when it's an Input

  • Compiler Version: 1.1.1
  • PHP Version: 7.1
  • Component: SDL

Description:

When we typing directive argument as an Input and then invoke this directive with argument correct struct value of passed argument of directive invocation is actualy empty object.

Steps To Reproduce:

Compile this:

directive @some(value: Bar) on OBJECT

input Bar {
   one: String!
   two: String
}

type Foo @some(value: {one: "Hey!"}){
    any: String!
}

then

$value = $document
    ->getTypeDefinition('Foo')
    ->getDirective('some')
    ->getPassedArgument('value')
;

var_dump($value);

Expected result:

Instance of InputInvocationBuilder filled with actual value ({one: "Hey!"}).

What happens instead?

Empty Instance of InputInvocationBuilder:

object(Railt\SDL\Reflection\Builder\Invocations\InputInvocationBuilder)#699 (0) {}

RFC Directives "DOCUMENT" location

Adding a DOCUMENT location allows you to add additional meta-data, for example:

directive @use(namespace: [String!]!) on DOCUMENT
directive @route(action: String!) on OBJECT | FIELD

Usage:

@use(namespace: "App\\Http\\Controllers")
@use(namespace: "App\\Http\\Controllers\\Admin")

schema {
    query: Query
}

type Query {
    users: [User]! @route(action: "ExampleController@usersAction")
    user: User @route(action: "ExampleController@userAction")
}

Add query type resolving to the router

  • Add $router->query('pattern', 'action') for GraphQL query action.
  • Add $router->mutation('pattern', 'action') for GraphQL mutationaction.
  • Add $router->subscription('pattern', 'action') for GraphQL subscription action.

RFC Extensions without bodies

In the case when the any type is extended by directives, the body of the type can be omitted:

type ExampleType { ... }

extend type ExampleType @addedDirective
interface ExampleInterface { ... }

extend interface ExampleInterface @addedDirective

Types Inheritance

The RL/SDL language contains two inheritance trees superimposed on each other:

  • Type tree
  • Implementations tree

Type tree

Available for private definitions (directives with SDL locations and arguments for wrappers)

AnyInputScalarEnumInterfaceObjectUnionDirective

Implementations tree

Available everywhere

AnyString"enum MyEnum"IDDateTimeFloatIntBool"interface MyInterface""type MyObj implements MyInterface""union X = ..." -- Depends on what includes"input MyInput"

The arrows (in the picture below) indicate the sequences where the type tree goes into the implementation tree.

123123

Thus we obtain:

directive @some(param: Scalar!) on FIELD

# ........
enum MyEnum { ... }
type Some @some(param: MyEnum) { ... } 
# Because MyEnum (impl tree) <- Enum (type tree) <- Scalar (type tree)

RFC Docblock

About

Add docblock syntax.

Example

# This is description
# of type Something
type Something {
    # This is optional description of "helloWorld" field
    helloWorld: String
}

Default value for argument defined by Input type

  • Railt Version: dev-master
  • PHP Version: 7.1
  • Component: Compiler

Description:

Incorrect validation of the default value for arguments specified as an Input type

Steps To Reproduce:

  1. Try to compile:
input Some {
    id: ID!
}

type Query {
    field(input: Some = {id: 23}): Any
}

Expected result:

OK (Successfully compilation)

What happens instead?

Result error:

TypeConflictException: (input: Some) can not be initialized by List "{"id":23}" in example.graphqls:6:10
#0 example.graphqls(6): (input: Some) 
#1 example.graphqls(6): (input: Some)
#2 example.graphqls(6): {field: ?}
#3 example.graphqls(5): Object<Query>
#4 example.graphqls(1): example.graphqls

Another mistake is an incorrect stacktrace (duplication of trace #0 and #1 indexes)

RFC 373: Nesting Interfaces

About

Add docblock syntax.

Example

interface Created {
    createdAt(format: String! = "RFC3339"): DateTime!
}

interface Updated {
    updatedAt(format: String! = "RFC3339"): DateTime
}

interface Timestamps implements Created, Updated {
}

######

type User implements Timestamps {
    # createdAt field required
    # updatedAt field required
}

Type collation does not work with directive invocation

  • Compiler Version: 1.1.1
  • PHP Version: 7.1
  • Component: SDL

Description:

Type collation does not work with directive invocation. Directive argument typing has no affect on directive argument invocation.

Steps To Reproduce:

Try to compile:

directive @some(value: [Bar!]!) on OBJECT

input Bar {
   one: String!
   two: String
}

type Foo @some(value: "String is not [Bar!]!"){
    any: String!
}

Expected result:

Compile time exception on directive argument type collation.

What happens instead?

Compiled successfully and string String is not [Bar!]! is actualy value of passed argument

Add route path disjunction

Example:

$router->on('some|any', 'Controller@action');

It must be the same in behavior as in:

$router->on('some', 'Controller@action');
$router->on('any', 'Controller@action');

RFC Add common mark support

Descriptions the description requires rendering support in the markdown.

"""
This is an example of list:
- Some
- Any
"""
type Example {}

Current behaviour:

\var_dump($example->getDescription());

// string(...) "This is an example of list:\n- Some\n- Any"

Required behaviour:

\var_dump($example->getDescription());

// string(...) "This is an example of list:\n- Some\n- Any"


$renderer = new HtmlRenderer();
\var_dump($example->getDescription($renderer));

// string(...) "<p>This is an example of list:</p><ul><li>Some</li><li>Any</li></ul>"

The method signature must be updated:

public function getDescription(Renderer $renderer = null): string
{
    return $renderer ? $renderer->render(/* description */) : /* description */;
}

Related spec:

https://github.com/facebook/graphql/blob/b0b517360e5eee43f337ce7006078ed4907a83ab/spec/Section%202%20--%20Language.md#descriptions

RFC 926: Multiline strings

About

Add multiline text support.

Example

type Some {
  example(value: String = "Single line string"): String
}

type Some {
    example(value: String = """
        Mutiline text
    """): String
}

Queies: Query Relations

Add a Query object that contains a list of relations:

/**
 * >> Input:
 * 
 * query {
 *   example {
 *     relation {
 *       id
 *     }
 *   }
 *   example_2 {
 *      id
 *   }
 * }
 * 
 */
class ExampleQuery extends AbstractQuery
{
    public function resolve(Query $query): iterable
    {
        /**
         * $relations = [
         *     'example' => [
         *          'relation'
         *      ],
         *     'example_2'
         * ]
         */
        $relations = $query->getRelations();

        /**
         * $plain = [
         *     'example.relation',
         *     'example_2'
         * ]
         */
        $plain = $query->getPlainRelations();
    }
}

Processing string literals

  • Railt Version: dev-master
  • PHP Version: 7.1
  • Component: Reflection

Description:

Processing of string literals does not work correctly.

Steps To Reproduce:

type Example {
    some: String @deprecated(reason: "Lol\\Troll\nexample\u00B6")
}
$result = $document->getDefinition('Example')
    ->getField('some')
    ->getDeprecationReson();

Expected

Result:

Lol\Troll
example¶

Actual

Result:

Lol\\Troll\nexample\u00B6

Roadmap Jan'18 - ...

Roadmap

Features:

  • #40: Directives "DOCUMENT" location
  • #43: Single types dictionary
  • #47: Extending Enums
  • #38: Nesting Interfaces

Bugfixes:

  • #87: Directive invocation argument value is empty when it's an Input.
  • #86: Type collation does not work with directive invocation.

Progress

  • Acceleration of the work of the Lexer more than 150 times.
  • New AST structure.
  • Error correction with type inheritance of another type.
  • New CallStack.
  • The compiler has removed the dictionary methods.
  • Added Directives "DOCUMENT" location (directive @xxx on DOCUMENT).
  • Added validation of location directives.
  • Added validation of the naming of the locations of directives.
  • Correction of cloning of a CallStack (empty trace in recursive exceptions).
  • Correction of the exception position in the source code from a variable (not from a file).
  • Simplify exclusion code (a separate class of CallStack renderer is created).
  • Added a CallStack Observer class.
  • A single dictionary of generalized types is created.
  • Significantly improved code and structure of tests.
  • Added support for recursive build of invokation types.
  • The possibility of schema naming was added (schema XXX {}).
  • Added support for multiple directives of the same name (type Some @directive @directive @directive {}).
  • Fix error message with recursive autoload (An attempt to load an invalid type that contains a non-existent type).
  • Correcting validation of arguments of directives and objects.

← Oct'17 - Dec'17

Roadmap Oct'17 - Dec'17

Reflection

  • Merge railt/parser and railt/reflection into single package.
  • Remove railt/support package.
  • Release alpha 1 (composer require railt/compiler 0.0.1)
  • Fix and improve arguments LSP (Reflection).
  • To get rid of checks of a status of the builder and to take out in validation from the outside (visitor?).
  • Remove "extend" from visible types and make preprocessing kernel (required for future additional features like #21 ).
  • Exceptions improvement: #23
  • Add type verifications.
  • Types inheritance bugfixes.
  • Waiting for hoaproject/Compiler#76
  • Waiting for hoaproject/Compiler#79
  • Waiting for a new release of hoa/compiler (forked into: https://github.com/railt/parser)
  • Release 1.0

Webonyx builder

  • Implement the base Webonyx support
  • Add support of Field Type
  • Add support of Argument Type
  • Add support of Schema Type
  • Add support of Object Type
  • Add support of Interface Type
  • Add support of Union Type
  • Add support of Directive Type
  • Add support of Scalar Type
  • Add support of Enum Type
  • Add support of Input Type
  • Add type invocations base hooks support

Jan'18 →

Improve Compiler Exceptions

Split internal and public compiler exceptions and add:

interface SchemaException extends \Throwable
{
    /**
     * Should return a character offset relative to the code line
     */
    public function getColumn(): int;

    /**
     * Should return a source code line on which the error occurred
     */
    public function getSourceLine(): string;
  
    /**
     * Returns a GraphQL internal trace of Compiler package.
     */
    public function getCompilerTrace(): iterable;
}

Requires:

  1. Issue: hoaproject/Compiler#75
    1.1) PR: hoaproject/Compiler#76
  2. Issue: hoaproject/Compiler#78
    2.1) PR: hoaproject/Compiler#79

RFC Add namespaces support

At the moment all GraphQL types share one global namespace. This is also true for all of the fields in mutation/subscription type. It can be a concern for bigger projects which may contain several loosely-coupled parts in a GraphQL schema.

It is suggested to reserve a symbol / for the support of the namespaces:

interface Common/Versioned {
  id: String!
  version: Long!
}

type Blog/Article implements Common/Versioned {
  id: String!
  version: Long!
  title: String!
  text: String
  author: Author
}

Queries: Lazy resolving

This issue follows #3

/**
 * Lazy resolving
 */
class UsersQuery extends AbstractQuery
{
    public function resolve(Query $query): iterable
    {
        yield 'id'    => 23;
        yield 'login' => 'Kirill';

        // Variant 1:
        // Resolved when input query require 'comments' relation
        yield 'comments' => $query->then(function(Query $sub) {
            // A:
            yield 'id'     => 1;
            yield 'body'   => 'some text';

            // B:
            return ['id' => 2, 'body' => 'some text'];
        });
        
        // Variant 2:
        yield $query->when('comments')->then(function(Query $sub) {
             // Same with v1 
        });
    }
}

RFC Custom Wrapping Types (Generics)

Just a bookmark )

wrapper Page($ofType: Type) {
    total: Int
    list: [$ofType!]!
}

type Query {
    users(page: Int, perPage: Int): Page(ofType: User)
}

RFC Extending Enums

enum Category {
  PROGRAMMING_LANGUAGES
  API_DESIGN
}

extend enum Category {
  UI_DESIGN
}

This syntax should work same as extend type (preprocessing).

Specifying directives on the arguments of directive declarations

Positive example test:

directive @validExample(
    arg: String @deprecated(reason: "Because")
) on ARGUMENT_DEFINITION

Negative example test:

directive @invalidExample(
    arg: String @invalidExample(arg: "references itself")
) on ARGUMENT_DEFINITION

At this time, support for such directives is not implemented:

$directives = $document->getTypeDefinition('validExample')
    ->getArgument('arg')
    ->getDirectives();

\var_dump(\count($directives));
// Return 0
// But 1 expected

RFC Inner types

As one of the encapsulation implementations, it makes sense to implement nested type support:

type Example {
    field: String
    field2: ID!
    field3: InnerType

    type InnerType {
        field: Example
    }
}

It is necessary to solve the problem of accessing external dependencies in case of name conflicts:

type Overrided {}

type Example {
    field: Overrided
    type Overrided { ... }
}

There is a conflict of names. Do I need to use an external or internal Overrided type? Obviously internal, but how in this case to appeal to the external?

Similar ideas: https://developers.google.com/protocol-buffers/docs/proto3#nested

Incorrect (uninformative) error message when type conflicts occur in the schema

  • Railt Version: dev-master
  • PHP Version: 7.1
  • Component: Compiler

Description:

subj

Steps To Reproduce:

  1. Try to compile:
schema {
    query: String
}

Expected result:

Error, like: Can not compile schema, because query can not contain Scalar "String"

What happens instead?

Return value of Railt\Compiler\Reflection\Builder\Definitions\SchemaBuilder::fetchType() must be an instance of Railt\Reflection\Contracts\Definitions\ObjectDefinition, instance of Railt\Reflection\Standard\Scalars\StringType returned

Routing improvements

Divide the routing component into two component parts:

  • Routing
  • Decorators

Routing

The route must return complete data for the response, including all available relations.

Definitions

The following methods should be implemented:

  • ->query('name', '...'): Must respond to query requests.
  • ->mutation('name', '...'): Must respond to mutation requests.
  • ->subscription('name', '...'): Must respond to subscription requests.
  • ->on('name', '...'): Must respond to all requests.

The second argument is a router handler (callable or string definition ClassName@action).

The action should provide complete information about the:

  • Relations
  • Arguments
  • Field type
  • ...other

Example:

schema {
    query: Query
}

type Query {
    users: [User]!
}

type User {
    id: ID!
    name: String!
    friends: [User]!
    createdAt: DateTime!
}
$router->query('users', function (Input $input) {
    $query = new DatabaseBuilder();

    if ($input->hasRelation('friends')) {
        $query->leftJoin(...);
    }
    
    return $query->get();
});

Decorators

The decorators system should provide the ability to manage and format already generated data. A class must implement at least three methods:

Middleware

Definition ->middleware('name', '...'). The first argument contains the name and group of the middlevare, the second is the response handler. The nesting group is separated by a "dot" (.) symbol. To use this decoration system - it must be declared in routs explicitly.

Example:

// Definition
$decorators->middleware('example', function ($input, $output) { ... });

// Usage the "example" middleware
$router->on('users', function (...) { ... })->middleware('example');

Example with groups:

// Definition
$decorators->middleware('group-example.test1', function ($input, $output) { ... });
$decorators->middleware('group-example.test2', function ($input, $output) { ... });
$decorators->middleware('group-example', function ($input, $output) { ... });

// Usage of "group-example", "group-example.test1" and "group-example.test2" middleware
$router->on('users', function (...) { ... })->middleware('group-example'); 

Path

The definition of "path decorators" should point explicitly to the field in the query inheritance chain (the collection indexes should not be taken into account when compiling the path).

Example

// Processing the field "createdAt" for the GraphQL query: "{ users { friends { createdAt } }"
$decorators->path('users.friends.createdAt', function (...) { ... });

Type-hint

"Type-hint" decorators must respond to the type matching in the declaration and the return result (using the instanceof operator).

Example:

// Processing the all fields which return a type that is one of the `DateTimeInterface` children.
$decorator->type(\DateTimeInterface::class, function (...) { ... });

The type must include the NotNull modifier when it is extended

  • Railt Version: dev-master
  • PHP Version: 7.1
  • Component: Reflection

Description:

The type must include the NotNull modifier when it is extended

Steps To Reproduce:

type Some {
    id: String
}

extend type Some {
    id: ID!
}

Expected result:

type Some {
    id: ID!
}

What happens instead?

type Some {
    id: ID
}

Queries: Query with "dot-notation"

Add query resolving over "dot-notation":

/**
 * Original
 */
class UsersQuery extends AbstractQuery
{
    public function resolve(Query $query): iterable
    {
        return [
            'id'       => 23,
            'login'    => 'Kirill',
            'comments' => [
                [
                    'id'   => 1,
                    'body' => 'some text',
                ],
                [
                    'id'   => 2,
                    'body' => 'some text',
                ],
            ],
        ];
    }
}

/**
 * RFC Advanced Usage:
 *  - Install "subrelations" over "dot" notation
 */
class UsersQuery extends AbstractQuery
{
    public function resolve(Query $query): iterable
    {
        yield 'id'          => 23;
        yield 'login'       => 'Kirill';
        yield 'comments.id'     => 1;
        yield 'comments.body'   => 'some text';
        yield 'comments.id'     => 2;
        yield 'comments.body'   => 'some text';
    }
}

Add type-hint intercepts of return types

Example:

$router->on('date', function() {
    return new DateTime();
});

Add when method for interception of router actions return types.

$router->when(\DateTimeInterface::class, function(\DateTime $date): string {
    return $date->format(\DateTime::RFC3339);
});

Update documentation

Quick start is out of date

Environment

  • Railt Version: dev-master
  • PHP Version: 7.1

Directive invocation. Test for undeclared argument

  • Railt Version: dev-master
  • PHP Version: 7.1
  • Component: Reflection

Description:

Directive invocation allows undefinded/undeclared argument.

Steps To Reproduce:

Try to compile:

directive @some(foo: String)  on TYPE_DEFINITION

type Other @some(bar: "Hey! Argument bar wasn't specified for this directive")  {
    
}

Expected result:

Exception "unknown argument bar"

What happens instead?

Compiled successfully

RFC Single types dictionary

At the moment there is no generalized type dictionary. It is proposed to introduce it, they will figure inside the language, and in the future they are used for specifying type-hints in #21

Unlike #24 it is proposed first to implement a linear list without a hierarchy.

Input
Scalar
Enum
Interface
Object
Union
Directive

Controversial Places

  1. Is the extend xxx an independent type or is it just a preprocessor directive?
  2. Should I refer to types in lowercase (Object -> object) to distinguish them from names or just simply reserve them?

Reflection returns uncatched fatal exception on implementation from type instead interface

  • Railt Version: dev-master
  • PHP Version: 7.1
  • Component: SDL

Description:

Trying to implement type from another type return FatalThrowableError)

Steps To Reproduce:

Create two types
Implement one type from other, e.g.:

type ApiResponse {
    success: Boolean!
}

type LoginResponse implements ApiResponse {
    token: String!
    success: Boolean!
}

Expected result:

Return cathed exception

What happens instead?

Return fatal exception

Schema: Extenders

Definition:

/** @var Serafim\Railgun\Endpoint $endpoint */
$endpoint = new Endpoint('example');

/** @var Serafim\Railgun\Schema\Fields $fields */
$fields = $endpoint->getSchemas()->get(Fields::class);

// Extend definitions (like VO)
$fields->extend('timestamps', function(
    string $created = 'created_at', 
    string $updated = 'updated_at'
) use ($fields) {
    yield $created => $fields->string();
    yield $updated => $fields->string();
});

Usage:

class UserType extends AbstractObjectType
{
    public function getFields(Fields $schema): iterable
    {
        yield 'id' => $schema->id();
        yield from $schema->timestamps(); // created_at + updated_at
    }
}

Reflection stabilization phase

  • Recheck the interfaces and do not break anymore.
  • Complete Reflection builders
    • Field Type
    • Argument Type
    • Schema Type
    • Object Type
    • Interface Type
    • Union Type
    • Directive Type
    • Directive Invcation
    • Scalar Type
    • Enum Type
    • Input Type
    • Extend Type
  • Other
    • Schema Autoloading
    • Persising (Cache)
  • A more than 90% of the component code coverage is required.

RFC Arguments by default

Adding default arguments for Fields and Inputs will require syntactic change of the GraphQL queries, so at the run-in phase, the features are enough to implement them at the Directive level.

Usage:

directive @route(action: String! @default) on OBJECT | FIELD 
                                # ^^^^^  Adding "@default" directive

type Query {
    users: [User]! @route("ExampleController@usersAction")
                       # ^^^ Use an optional argument name 
}

Instead of:

directive @route(action: String!) on OBJECT | FIELD 

type Query {
    users: [User]! @route(action: "ExampleController@usersAction")
                        # ^^^^^^  Explicit specification of the argument
}

RFC Deprecation descriptions markdowns renderer

type Example {
    field: String @deprecated(reason: """
Because:
- This is an example.
- Because I can!
""")
}

Current behaviour:

\var_dump($field->getDeprecationReason());

// string(...) "Because:\n- This is an example.\n- Because I can!"

Required behaviour:

\var_dump($field->getDeprecationReason());

// string(...) "Because:\n- This is an example.\n- Because I can!"

$renderer = new HtmlRenderer();
\var_dump($field->getDeprecationReason($renderer));

// string(...) "<p>Because:</p><ul><li>This is an example.</li><li>Because I can!</li></ul>"

The method signature must be updated:

public function getDeprecationReason(Renderer $renderer = null): string
{
    return $renderer ? $renderer->render(/* reason */) : /* reason */;
}

Related with:

The Schemа should include a query field

  • Railt Version: dev-master
  • PHP Version: 7.1
  • Component: Compiler

Description:

The Schemа should include a query field

Steps To Reproduce:

  1. Try to compile this file:
schema {}

Expected result:

Error: Schema shoud contain an query field.

What happens instead?

Nothing

RFC Add inline descriptions support

Changes in the GraphQL SDL standard assume support for single-line comments for arguments and the enum values:

"""
A simple GraphQL schema which is well described.
"""
type Query {
  """
  Translates a string from a given language into a different language.
  """
  translate(
    "The original language that `text` is provided in."
    fromLanguage: Language

    "The translated language to be returned."
    toLanguage: Language

    "The text to be translated."
    text: String
  ): String
}

"""
The set of languages supported by `translate`.
"""
enum Language {
  "English"
  EN

  "Russian"
  RU

  "Chinese"
  CH
}

Custom values for enum

I think that it's worth implementing support for custom values for enum

Example:

type User {
  updatedAt(format: DateTimeFormat = RFC3339): DateTime!
  createdAt(format: DateTimeFormat = RFC3339): DateTime!
}

enum DateTimeFormat {
    RFC3339: "Y-m-d\TH:i:sO"
    ISO8601: "Y-m-d\TH:i:sP"
}

What do you think about this?

Incorrect behaviour while define docblock into schema

  • Railt Version: dev-master
  • PHP Version: 7.1
  • Component: Compiler

Description:

subj

Steps To Reproduce:

  1. Try to compile this file:
schema {
    """
    Description
    """
    query: Query
}

Expected result:

Error: Schema could not contain description blocks

What happens instead?

Error:

TypeNotFoundException: Type "
    Description
    " not found and could not be loaded in example.graphqls:1:0
#0 example.graphqls(1): Schema<DefaultSchema>
#1 example.graphqls(1): example.graphqls

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.