A PHP implementation of the GraphQL specification based on the JavaScript reference implementation

Overview

GraphQL

Build Status Coverage Status Scrutinizer Code Quality License Backers on Open Collective Sponsors on Open Collective

This is a PHP implementation of the GraphQL specification based on the JavaScript reference implementation.

Related projects

Requirements

  • PHP version >= 7.1
  • ext-mbstring

Table of contents

Installation

Run the following command to install the package through Composer:

composer require digiaonline/graphql

Example

Here is a simple example that demonstrates how to build an executable schema from a GraphQL schema file that contains the Schema Definition Language (SDL) for a Star Wars-themed schema (for the schema definition itself, see below). In this example we use that SDL to build an executable schema and use it to query for the name of the hero. The result of that query is an associative array with a structure that resembles the query we ran.

use Digia\GraphQL\Language\FileSourceBuilder;
use function Digia\GraphQL\buildSchema;
use function Digia\GraphQL\graphql;

$sourceBuilder = new FileSourceBuilder(__DIR__ . '/star-wars.graphqls');

$schema = buildSchema($sourceBuilder->build(), [
    'Query' => [
        'hero' => function ($rootValue, $arguments) {
            return getHero($arguments['episode'] ?? null);
        },
    ],
]);

$result = graphql($schema, '
query HeroNameQuery {
  hero {
    name
  }
}');

\print_r($result);

The script above produces the following output:

Array
(
    [data] => Array
    (
        [hero] => Array
        (
            [name] => "R2-D2"
        )
        
    )
    
)

The GraphQL schema file used in this example contains the following:

schema {
    query: Query
}

type Query {
    hero(episode: Episode): Character
    human(id: String!): Human
    droid(id: String!): Droid
}

interface Character {
    id: String!
    name: String
    friends: [Character]
    appearsIn: [Episode]
}

type Human implements Character {
    id: String!
    name: String
    friends: [Character]
    appearsIn: [Episode]
    homePlanet: String
}

type Droid implements Character {
    id: String!
    name: String
    friends: [Character]
    appearsIn: [Episode]
    primaryFunction: String
}

enum Episode { NEWHOPE, EMPIRE, JEDI }

Creating a schema

In order to execute queries against your GraphQL API, you first need to define the structure of your API. This is done by creating a schema. There are two ways to do this, you can either do it using SDL or you can do it programmatically. However, we strongly encourage you to use SDL, because it is easier to work with. To make an executable schema from SDL you need to call the buildSchema function.

The buildSchema function takes three arguments:

  • $source The schema definition (SDL) as a Source instance
  • $resolverRegistry An associative array or a ResolverRegistry instance that contains all resolvers
  • $options The options for building the schema, which also includes custom types and directives

To create the Source instance you can use the provided FileSourceBuilder or MultiFileSourceBuilder classes.

Resolver registry

The resolver registry is essentially a flat map with the type names as its keys and their corresponding resolver instances as its values. For smaller projects you can use an associative array and lambda functions to define your resolver registry. However, in larger projects we suggest that you implement your own resolvers instead. You can read more about resolvers under the Resolvers section.

Associative array example:

$schema = buildSchema($source, [
    'Query' => [
        'hero' => function ($rootValue, $arguments) {
            return getHero($arguments['episode'] ?? null);
        },
    ],
]);

Resolver class example:

$schema = buildSchema($source, [
    'Query' => [
        'hero' => new HeroResolver(),
    ],
]);

Resolver middleware

If you find yourself writing the same logic in multiple resolvers you should consider using middleware. Resolver middleware allow you to efficiently manage functionality across multiple resolvers.

Before middleware example:

$resolverRegistry = new ResolverRegristry([
    'Query' => [
        'hero' => function ($rootValue, $arguments) {
            return getHero($arguments['episode'] ?? null);
        },
    ],
], [
    'middleware' => [new BeforeMiddleware()],
]);
$schema = buildSchema($source, $resolverRegistry);
class BeforeMiddleware implements ResolverMiddlewareInterface
{
    public function resolve(callable $resolveCallback, $rootValue, array $arguments, $context, ResolveInfo $info) {
        $newRootValue = $this->doSomethingBefore();
        return $resolveCallback($newRootValue, $arguments, $context, $info);
    }
}

After middleware example:

$resolverRegistry = new ResolverRegristry([
    'Query' => [
        'hero' => function ($rootValue, $arguments) {
            return getHero($arguments['episode'] ?? null);
        },
    ],
], [
    'middleware' => [new AfterMiddleware()],
]);
$schema = buildSchema($source, $resolverRegistry);
class AfterMiddleware implements ResolverMiddlewareInterface
{
    public function resolve(callable $resolveCallback, $rootValue, array $arguments, $context, ResolveInfo $info) {
        $result = $resolveCallback($rootValue, $arguments, $context, $info);
        $this->doSomethingAfter();
        return $result;
    }
}

Resolver middleware can be useful for a number of things; such as logging, input sanitization, performance measurement, authorization and caching.

If you want to learn more about schemas you can refer to the specification.

Execution

Queries

To execute a query against your schema you need to call the graphql function and pass it your schema and the query you wish to execute. You can also run mutations and subscriptions by changing your query.

$query = '
query HeroNameQuery {
  hero {
    name
  }
}';

$result = graphql($schema, $query);

If you want to learn more about queries you can refer to the specification.

Resolvers

Each type in a schema has a resolver associated with it that allows for resolving the actual value. However, most types do not need a custom resolver, because they can be resolved using the default resolver. Usually these resolvers are lambda functions, but you can also define your own resolvers by extending AbstractTypeResolver or AbstractFieldResolver. Alternatively you can also implement the ResolverInterface directly.

A resolver function receives four arguments:

  • $rootValue The parent object, which can also be null in some cases
  • $arguments The arguments provided to the field in the query
  • $context A value that is passed to every resolver that can hold important contextual information
  • $info A value which holds field-specific information relevant to the current query

Lambda function example:

function ($rootValue, array $arguments, $context, ResolveInfo $info): string {
    return [
        'type'       => 'Human',
        'id'         => '1000',
        'name'       => 'Luke Skywalker',
        'friends'    => ['1002', '1003', '2000', '2001'],
        'appearsIn'  => ['NEWHOPE', 'EMPIRE', 'JEDI'],
        'homePlanet' => 'Tatooine',
    ];
}

Type resolver example:

class HumanResolver extends AbstractTypeResolver
{
    public function resolveName($rootValue, array $arguments, $context, ResolveInfo $info): string
    {
        return $rootValue['name'];
    }
}

Field resolver example:

class NameResolver extends AbstractFieldResolver
{
    public function resolve($rootValue, array $arguments, $context, ResolveInfo $info): string
    {
       return $rootValue['name'];
    }
}

The N+1 problem

The resolver function can return a value, a promise or an array of promises. This resolver function below illustrates how to use promise to solve the N+1 problem, the full example can be found in this test case.

$movieType = newObjectType([
    'fields' => [
        'title'    => ['type' => stringType()],
        'director' => [
            'type'    => $directorType,
            'resolve' => function ($movie, $args) {
                DirectorBuffer::add($movie['directorId']);
                
                return new Promise(function (callable $resolve, callable $reject) use ($movie) {
                    DirectorBuffer::loadBuffered();
                    $resolve(DirectorBuffer::get($movie['directorId']));
                });
            }
        ]
    ]
]);

Variables

You can pass in variables when executing a query by passing them to the graphql function.

$query = '
query HeroNameQuery($id: ID!) {
  hero(id: $id) {
    name
  }
}';

$variables = ['id' => '1000'];

$result = graphql($schema, $query, null, null, $variables);

Context

In case you need to pass in some important contextual information to your queries you can use the $contextValues argument on graphql to do so. This data will be passed to all of your resolvers as the $context argument.

$contextValues = [
    'currentlyLoggedInUser' => $currentlyLoggedInUser,
];

$result = graphql($schema, $query, null, $contextValues, $variables);

Scalars

The leaf nodes in a schema are called scalars and each scalar resolves to some concrete data. The built-in, or specified scalars in GraphQL are the following:

  • Boolean
  • Float
  • Int
  • ID
  • String

Custom scalars

In addition to the specified scalars you can also define your own custom scalars and let your schema know about them by passing them to the buildSchema function as part of its $options argument.

Custom Date scalar type example:

$dateType = newScalarType([
    'name'         => 'Date',
    'serialize'    => function ($value) {
        if ($value instanceof DateTime) {
            return $value->format('Y-m-d');
        }
        return null;
    },
    'parseValue'   => function ($value) {
        if (\is_string($value)){
            return new DateTime($value);
        }
        return null;
    },
    'parseLiteral' => function ($node) {
        if ($node instanceof StringValueNode) {
            return new DateTime($node->getValue());
        }
        return null;
    },
]);

$schema = buildSchema($source, [
    'Query' => QueryResolver::class,
    [
        'types' => [$dateType],
    ],
]);

Every scalar has to be coerced, which is done by three different functions. The serialize function converts a PHP value into the corresponding output value. TheparseValue function converts a variable input value into the corresponding PHP value and the parseLiteral function converts an AST literal into the corresponding PHP value.

Advanced usage

If you are looking for something that isn't yet covered by this documentation your best bet is to take a look at the tests in this project. You'll be surprised how many examples you'll find there.

Integration

Laravel

Here is an example that demonstrates how you can use this library in your Laravel project. You need an application service to expose this library to your application, a service provider to register that service, a controller and a route for handling the GraphQL POST requests.

app/GraphQL/GraphQLService.php

class GraphQLService
{
    private $schema;

    public function __construct(Schema $schema)
    {
        $this->schema = $schema;
    }

    public function executeQuery(string $query, array $variables, ?string $operationName): array
    {
        return graphql($this->schema, $query, null, null, $variables, $operationName);
    }
}

app/GraphQL/GraphQLServiceProvider.php

class GraphQLServiceProvider
{
    public function register()
    {
        $this->app->singleton(GraphQLService::class, function () {
            $schemaDef = \file_get_contents(__DIR__ . '/schema.graphqls');

            $executableSchema = buildSchema($schemaDef, [
                'Query' => QueryResolver::class,
            ]);

            return new GraphQLService($executableSchema);
        });
    }
}

app/GraphQL/GraphQLController.php

class GraphQLController extends Controller
{
    private $graphqlService;

    public function __construct(GraphQLService $graphqlService)
    {
        $this->graphqlService = $graphqlService;
    }

    public function handle(Request $request): JsonResponse
    {
        $query         = $request->get('query');
        $variables     = $request->get('variables') ?? [];
        $operationName = $request->get('operationName');

        $result = $this->graphqlService->executeQuery($query, $variables, $operationName);

        return response()->json($result);
    }
}

routes/api.php

Route::post('/graphql', 'app\GraphQL\GraphQLController@handle');

Contributors

This project exists thanks to all the people who contribute. Contribute.

Backers

Thank you to all our backers! 🙏 Become a backer

Sponsors

Support this project by becoming a sponsor. Your logo will show up here with a link to your website. Become a sponsor

License

See LICENCE.

Comments
  • Deferred resolving doesn't seem to work

    Deferred resolving doesn't seem to work

    There's something wrong with it. In the test we end up calling loadBuffered() twice, which shouldn't happen. There's also both $directorIds and $authors, which seems wrong. Gotta look this through carefully.

    bug execution performance 
    opened by Jalle19 15
  • Change a bunch of assertions from assertEquals to assertSame

    Change a bunch of assertions from assertEquals to assertSame

    Luckily, nothing did break because of this, so the code seems fine as is! Still, going forward we should aim to use assertSame wherever possible, as it uses triple equal sign comparison.

    opened by spawnia 15
  • Add a SourceBuilderInterface along with two basic implementations

    Add a SourceBuilderInterface along with two basic implementations

    The FileSourceBuilder just does \file_get_contents() on a file, so it's not that useful unless we change the API to accept only Source and not string.

    The DirectorySourceBuilder is more interesting since it allows people to split their giant SDL files into multiple logic units (e.g. one for Relay, one for common sorting/ordering stuff, etc.).

    enhancement public api SDL documentation 
    opened by Jalle19 10
  • Deferred Resolvers

    Deferred Resolvers

    What did you do?

    I created a resolver that resolves to another type.

    What did you expect to happen?

    I expected to be able to grab all sub types in a single database query.

    What actually happened?

    I was only able to resolve to a single type each time for all the sub types which led to 100s of database queries.

    What version of this project are you using?

    master branch.

    Please include code that reproduces the issue. The best reproductions are self-contained scripts with minimal dependencies.

    Code is not included as this is a new feature.

    question execution 
    opened by asheliahut 9
  • [Question] Maturity?

    [Question] Maturity?

    My team has been using Youshido GraphQL for a project over the last 9 months or so. It gets the job done, but it's slow, not documented well, and PRs take forever to get merged and released. So my team has been debating the pros/cons of forking it and driving it in a more performant direction.

    One of the performance-related PRs on that project appears to have come from the developers of this project… which leads me to ask some questions:

    1. Is this project ready for heavy production loads by teams outside of yourselves?

    2. Is there a migration path from Youshido to this one?

    3. What kind of stability/support would another team be able to expect from this package?

    4. Are you open to third-party PRs, and is there a reasonably straightforward process for contributing back upstream?

    In other words, how mature is this as an open-source project?

    question 
    opened by skyzyx 8
  • Exception handling

    Exception handling

    We need to think carefully about how we implement exception handling. There are three things to keep in mind:

    • normally, if one field fails to resolve with an exception, the rest of the fields should still resolve and the query should just include errors
    • this means exceptions get swallowed, so applications that report uncaught exceptions (technically these are caught, but only implicitly) need an easy way to access the underlying exceptions after the full query is resolved
    • while developing you may not want GraphQL to swallow exceptions, instead you may want eventual exceptions to remain uncaught so they bubble up to the application's exception handler (which then usually renders it with a stack trace)
    enhancement execution 
    opened by crisu83 7
  • Add GraphQL contracts support

    Add GraphQL contracts support

    This is an example of implementing of https://github.com/php-graphql/type-system-contracts into an existing solution which was discussed in https://github.com/webonyx/graphql-php/issues/560

    I ask you to make a codereview and draw conclusions based on what we are correcting in the php-graphql/type-system-contracts package and digiaonline/graphql-php.

    Problems I encountered while implementing interfaces:

    Major

    1. GraphQL Argument, Field and InputField require the obligatory indication of type. Nulls are not allowed. But the digiaonline implementation allows null as a type. 1.1) GraphQL Contract: https://github.com/php-graphql/type-system-contracts/blob/master/src/Common/TypeAwareInterface.php#L24 1.2) GraphQL Spec: 1.2.1) https://github.com/graphql/graphql-js/blob/master/src/type/definition.d.ts#L520 1.2.2) https://github.com/graphql/graphql-js/blob/master/src/type/definition.d.ts#L507 1.2.3) https://github.com/graphql/graphql-js/blob/master/src/type/definition.d.ts#L804 1.3) Digia Implementation: https://github.com/digiaonline/graphql-php/blob/master/src/Type/Definition/TypeTrait.php#L15 1.4) Solution: Implementation additional getNullableType method: https://github.com/digiaonline/graphql-php/commit/074958e403b579ce24ee56d9767b1c85501110a8

    2. Digia implementation does not support repeatable directives. Now the implementation throws an exception if this method is called: https://github.com/SerafimArts/graphql-php/blob/graphql-contracts/src/Type/Definition/Directive.php#L71-L74

    Behavior change

    1. Any wrapping types in GraphQL are output and input. This required a change in the logic of the isInputType and isOutputType functions. 1.1) Solution: https://github.com/digiaonline/graphql-php/commit/d006fabac4917faefc96bd7913d546eaa98fda89

    Minor

    1. Incompatibility SchemaInterface with Schema implementation: 1.1) SchemaInterface::getPossibleTypes and Schema::getPossibleTypes 1.2) SchemaInterface::isPossibleType and Schema::isPossibleType 1.3) SchemaInterface::getQueryType and Schema::getQueryType 1.4) SchemaInterface::getMutationType and Schema::getMutationType 1.5) SchemaInterface::getSubscriptionType and Schema::getSubscriptionType

    2. Some classes required a change of signatures from getSomeElements(): array to getSomeElements(): iterable (for example Schema::getTypeMap(): array -> Schema::getTypeMap(): iterable).

    3. Some types requires the addition of hasSome methods (for example Directive::hasLocation(xxx)).

    4. In the current implementation of contracts, an AbstractTypeInterface is not an instance of NamedTypeInterface. Perhaps it is worth fixing in a contracts package?

    opened by SerafimArts 6
  • How to resolve inner type field defined in graphql language

    How to resolve inner type field defined in graphql language

    Hey,

    I'm giving a try to this lib as a potential replacement to Youshido's one (that we actually use in prod).

    I like the fact that we can write GraphQL schema using SDL. But I wonder, how can I resolve a field inside a type defined in the SDL fashion?

    For example:

    schema {
      query: Query
    }
    
    type Query {
      offer(id: ID!): Offer
      user(id: ID!): User
    }
    
    type Offer {
      id: ID!
      image: Image!
    }
    
    type User {
      id: ID!
      avatar: Avatar
    }
    
    type Avatar {
       image: Image!
       placeholder: Boolean!
    }
    
    type Image {
      path: String!
      src(q: Int, w: Int, h: Int, fit: String, fm: String, dpr: String, download: Boolean): String!
    }
    

    And I'd like to write the resolver for the src field in Image type. Do I necessary have to define the complete Image type programmatically ?

    With Youshido's library I had something like the following:

    <?php
    
    namespace ST\GraphQL\Query\Type;
    
    use ST\GraphQL\Query\Field\Image\SrcField;
    use Youshido\GraphQL\Type\Object\AbstractObjectType;
    use Youshido\GraphQL\Type\Scalar\StringType;
    use Youshido\GraphQL\Type\NonNullType;
    
    class ImageType extends AbstractObjectType
    {
        public function build($config)
        {
            $config
                ->addField('path', new NonNullType(new StringType()))
                ->addField(new SrcField())
            ;
        }
    
        public function getName()
        {
            return "Image";
        }
    
        public function getDescription()
        {
            return "An image";
        }
    }
    
    <?php
    
    namespace ST\GraphQL\Query\Field\Image;
    
    use ST\GraphQL\ExecutionContext;
    use Youshido\GraphQL\Config\Field\FieldConfig;
    use Youshido\GraphQL\Execution\ResolveInfo;
    use Youshido\GraphQL\Field\AbstractField;
    use Youshido\GraphQL\Type\NonNullType;
    use Youshido\GraphQL\Type\Scalar;
    
    class SrcField extends AbstractField
    {
        public function build(FieldConfig $config)
        {
            $config
                ->addArguments([
                    "q"         => new Scalar\IntType(),
                    "w"         => new Scalar\IntType(),
                    "h"         => new Scalar\IntType(),
                    "fit"       => new Scalar\StringType(),
                    "fm"        => new Scalar\StringType(),
                    "dpr"       => new Scalar\FloatType(),
                    "download"  => new Scalar\BooleanType(),
                ])
            ;
        }
    
        public function getType()
        {
            return new NonNullType(new Scalar\StringType());
        }
    
        public function getName()
        {
            return "src";
        }
    
        public function resolve($value, array $args, ResolveInfo $info)
        {
            /** @var ExecutionContext $executionContext */
            $executionContext = $info->getExecutionContext();
    
            if (!array_key_exists("q", $args)) {
                $args["q"] = 90;
            }
    
            if (!array_key_exists("fit", $args)) {
                $args["fit"] = "crop";
            }
    
            if (!array_key_exists("dpr", $args)) {
                $args["dpr"] = 2;
            }
    
            $packageName = $value['package'] ?? null;
    
            return $executionContext->getGlideUrlGenerator()->generate($value['path'], $args, $packageName);
        }
    }
    

    I'd like to keep every possible definition in the graphql file by using SDL to maximize portability.

    Thanks guys :)

    question 
    opened by benjamindulau 5
  • Can't set description of field with legacy comments

    Can't set description of field with legacy comments

    What did you do?

    Created a schema with following strin

    type Query {
        # Name of the app.
        appName: String
    }
    

    What did you expect to happen?

    That appName has the description

    What actually happened?

    No description

    What version of this project are you using?

    dev-master 383d875)

    question 
    opened by olivernybroe 5
  • Upgrade phpstan and add config file

    Upgrade phpstan and add config file

    Upgrades phpstan and moves settings into a config file.

    The latest version of phpstan has some new checks which have highlighted a few issues (mostly around casting to string and making league/container specific calls)

    code quality refactor 
    opened by symm 4
  • Name issue with Scalar Introspection

    Name issue with Scalar Introspection

    What did you do?

    We have two scalars named Date and Time that we use in a few places. We also have a test which introspects our types and makes sure we're documenting them all. Introspection of these types seems to be doing something odd with names, possibly calling the object name (clashing with php's date() and time())

    What did you expect to happen?

    With the following query:

    {
      __schema {
        types {
          name
          description
        }
      }
    }
    

    We would expect the result for Date to be:

     [
       "name" => "Date"
       "description" => "A date in YYYY-MM-DD format."
     ]
    

    and the result for Time to be:

    [
      "name" => "Time"
       "description" => "Representation of a time in 24H format (e.h. 19:00)."
    ]
    

    What actually happened?

    We are seeing the following responses for Date:

     [
       "name" => null
       "description" => "A date in YYYY-MM-DD format."
     ]
    

    and for Time:

    [
      "name" => "1531475584"
       "description" => "Representation of a time in 24H format (e.h. 19:00)."
    ]
    

    The query for Date also includes the following error:

    [{"message":"date() expects at most 2 parameters, 4 given","locations":[{"line":4,"column":13}],"path":["__type","name"]}]
    

    What version of this project are you using?

    I've created a branch of the project which includes a test which breaks due to this issue:

    https://github.com/digiaonline/graphql-php/compare/master...cgrice:bug-with-scalar-introspection

    There's a commented out piece of code in the IntrospectionProvider which fixes the test, but I'm not sure this is the best approach! I couldn't find the piece of code which is directly causing the issue.

    opened by cgrice 4
  • error with php 8.1

    error with php 8.1

    hi i installed php 8.1 and upgraded laravel version to 8.7 when i run composer install getting this error in utils.php file in vendor (utils.php not class just php file) :

    Fatal error: Unparenthesized a ? b : c ? d : e is not supported. Use either (a ? b : c) ? d : e or a ? b : (c ? d : e) in /home/omid/Desktop/fanap-upgrade2/rad-api/vendor/digiaonline/graphql/src/Language/utils.php on line 167

    opened by omidxplimbo 0
  • How would I return a list of objects with total records

    How would I return a list of objects with total records

    Lets say I have a list of records I can return based on some value. And there are so many records I want to paginate over them. But the front end wants to make a nice navigation of page 1, 2, etc...

    If I design a schema like this

    type Orders{
      id: ID
      user_id: Int
      date: String
      ...
    }
    

    and I have a query like

    userOrders(user_id: Int!, limit: Int, offset:: Int): [Orders]
    

    I would like to return a result like

    {
      data: {
        userOrders: [
            { id:1,user_id:1,date:"2021-04-01",...},
            { id:2,user_id:1,date:"2021-04-11",...},
            ...
        ]
      },
      total: 124
    }
    

    is there a way to do this, or would I need to modify my type to include a "total" field?

    opened by ChrisJokinen 0
  • Error Reporting

    Error Reporting

    Is there already or can you folks please add a way for the graphql method to return errors when a query does not match the schema?

    Example

    Schema: type Query { category(id: [Int]!): [Category] <- expects and array of ids }

    Query: { category(id:2) { id title } }

    Result: null

    Wanted result: { "error": "Variable id is of type Int, expects array of Ints" }

    Thanks

    bug execution 
    opened by truecastdesign 1
  • Improving Performances

    Improving Performances

    I try to replace Youshido implementation because of the its catastrophic performance issues. It consumes a lot of CPU because of the way it resolves fields recursively.

    So I rewrote a portion of our GraphQL using digiaonline library and out-of-box, without any tweak (I just implemented it by the book following the documentation), it's even worse.

    For the exact same GraphQL query:

    Youshido: capture d ecran 2018-10-27 a 17 19 02

    Digiaonline: capture d ecran 2018-10-27 a 17 19 45

    Here are some profiling extracts: capture d ecran 2018-10-27 a 17 22 13

    capture d ecran 2018-10-27 a 17 35 27

    The number of calls just blew my mind :D

    Also, I'm not sure the Deferred system is working correctly, Promises are resolved a lot more that they should be. For instance Curl/Guzzle should be called only twice in this example but was called 143 times (like I didn't have deferred calls at all). Maybe that could be related.

    Here is the code example related to the deferred data loaders:

    public function resolveSelections($rootValue, array $arguments, $context = null, ?ResolveInfo $info = null): Promise
    {
        return new Promise($this->findActiveSelectionsForOfferBuffer->add($offerId));
    }
    

    Buffer:

    <?php
    use ST\CQRS\DAO\Elasticsearch\ResultSet;
    use ST\CQRS\Projection\Dictionary\Dictionary;
    
    abstract class AbstractSearchableBuffer
    {
        /**
         * Offer IDs
         *
         * @var array
         */
        protected $ids = [];
    
        /**
         * Per Offer Id results.
         *
         * offerId -> ResultSet
         *
         * @var array|ResultSet[]
         */
        protected $results;
    
        /**
         * @var boolean
         */
        protected $cacheEnabled;
    
        protected $dictionary;
    
        public function __construct(Dictionary $dictionary, $cacheEnabled = true)
        {
            $this->dictionary   = $dictionary;
            $this->ids          = [];
            $this->results      = [];
    
            $this->cacheEnabled = $cacheEnabled;
        }
    
        public function add(string $id, array $params = []): callable
        {
            $resolver = function (callable $resolve, callable $reject) use ($id) {
                $resultSet = $this->resolveQuery($id);
                $dictionaryResults = $this->dictionary->getMany(array_column($resultSet->items, 'id'));
    
                $resolve($dictionaryResults);
            };
    
            // id have been already resolved
            if ($this->cacheEnabled && array_key_exists($id, $this->results)) {
                return $resolver;
            }
    
            // id will be resolved
            $this->ids[$id] = $id;
    
            return $resolver;
        }
    
        protected function resolveQuery($id): ResultSet
        {
            // this id has been already resolved.
            if ($this->cacheEnabled && array_key_exists($id, $this->results)) {
                return $this->results[$id];
            }
    
            $resolvedIds = $this->findMany($this->ids);
    
            // map queries resolved value(s)
            $partialResults = array_map(function ($id) use ($resolvedIds) {
                return $resolvedIds[$id];
            }, $this->ids);
    
            $this->ids = [];
            $this->results = array_merge($this->results, $partialResults);
    
            return $this->results[$id];
        }
    
        /**
         * @param array $ids
         * @return ResultSet[] key => id
         */
        abstract protected function findMany(array $ids): array;
    }
    
    <?php
    
    use ST\CQRS\Projection\Dictionary\Dictionary;
    use ST\Projection\Frontoffice\Searchable\DAO\SelectionDAO;
    
    class FindActiveSelectionsForOfferBuffer extends AbstractSearchableBuffer
    {
        private $selectionDAO;
    
        public function __construct(SelectionDAO $selectionDAO, Dictionary $dictionary, $cacheEnabled = true)
        {
            $this->selectionDAO = $selectionDAO;
    
            parent::__construct($dictionary, $cacheEnabled);
        }
    
        protected function findMany(array $ids): array
        {
            return $this->selectionDAO->findActiveSelectionsForOffers($ids, 10);
        }
    }
    

    Do you plan on adding some caching strategy and/or recommandations?

    bug execution performance 
    opened by benjamindulau 11
  • Laravel integration package

    Laravel integration package

    Is your feature request related to a problem? Please describe.

    Lots of people use Laravel, and they expect there to be an easy to use service provider available

    Describe the solution you'd like

    Make a digiaonline/laravel-graphql package (naming is up for debate)

    enhancement public api documentation 
    opened by Jalle19 2
Releases(v1.1.0)
Owner
Digia
We share the passion for writing great code.
Digia
Monorepo of the PoP project, including: a server-side component model in PHP, a GraphQL server, a GraphQL API plugin for WordPress, and a website builder

PoP PoP is a monorepo containing several projects. The GraphQL API for WordPress plugin GraphQL API for WordPress is a forward-looking and powerful Gr

Leonardo Losoviz 265 Jan 7, 2023
Monorepo of the PoP project, including: a server-side component model in PHP, a GraphQL server, a GraphQL API plugin for WordPress, and a website builder

PoP PoP is a monorepo containing several projects. The GraphQL API for WordPress plugin GraphQL API for WordPress is a forward-looking and powerful Gr

Leonardo Losoviz 265 Jan 7, 2023
Syntax to query GraphQL through URL params, which grants a GraphQL API the capability to be cached on the server.

Field Query Syntax to query GraphQL through URL params, which grants a GraphQL API the capability to be cached on the server. Install Via Composer com

PoP 4 Jan 7, 2022
Tukio is a complete and robust implementation of the PSR-14 Event Dispatcher specification

Tukio is a complete and robust implementation of the PSR-14 Event Dispatcher specification. It supports normal and debug Event Dispatchers, both runtime and compiled Providers, complex ordering of Listeners, and attribute-based registration on PHP 8.

Larry Garfield 70 Dec 19, 2022
Generates OpenApi specification for Laravel, Lumen or Dingo using a configuration array and cebe/php-openapi

OpenApi Generator for Laravel, Lumen and Dingo. About The openapi-gen package provides a convenient way to create OpenApi specifications for Laravel,

Jean Dormehl 5 Jan 25, 2022
Best resources restful api for developers (with JSON:API standar specification design)

List API Best resources restful api for developers (with JSON:API standar specification design). API Resource Endpoint Name Resource Description Al Qu

Noval 2 Jan 18, 2022
Pure PHP implementation of GraphQL Server – Symfony Bundle

Symfony GraphQl Bundle This is a bundle based on the pure PHP GraphQL Server implementation This bundle provides you with: Full compatibility with the

null 283 Dec 15, 2022
GraPHPinator ⚡ 🌐 ⚡ Easy-to-use & Fast GraphQL server implementation for PHP

Easy-to-use & Fast GraphQL server implementation for modern PHP. Includes features from latest draft, middleware directives and modules with extra functionality.

Infinityloop.dev 34 Dec 14, 2022
GraphQL implementation with power of Laravel

Laravel GraphQL Use Facebook GraphQL with Laravel 5.2 >=. It is based on the PHP implementation here. You can find more information about GraphQL in t

Studionet 56 Mar 9, 2022
Test your PHP GraphQL server in style, with Pest!

Pest GraphQL Plugin Test your GraphQL API in style, with Pest! Installation Simply install through Composer! composer require --dev miniaturebase/pest

Minibase 14 Aug 9, 2022
Pure PHP realization of GraphQL protocol

Looking for Maintainers! Unfortunatelly, we cannot longer support this package and are looking for someone to take the ownership. Currently Only PRs w

null 714 Dec 21, 2022
Create REST and GraphQL APIs, scaffold Jamstack webapps, stream changes in real-time.

API Platform is a next-generation web framework designed to easily create API-first projects without compromising extensibility and flexibility: Desig

API Platform 7.7k Jan 7, 2023
This bundle provides tools to build a complete GraphQL server in your Symfony App.

OverblogGraphQLBundle This Symfony bundle provides integration of GraphQL using webonyx/graphql-php and GraphQL Relay. It also supports: batching with

Webedia - Overblog 720 Dec 25, 2022
GraphQL Bundle for Symfony 2.

Symfony 2 GraphQl Bundle Use Facebook GraphQL with Symfony 2. This library port laravel-graphql. It is based on the PHP implementation here. Installat

Sergey Varibrus 35 Nov 17, 2022
Laravel wrapper for Facebook's GraphQL

Laravel GraphQL Use Facebook's GraphQL with Laravel 6.0+. It is based on the PHP port of GraphQL reference implementation. You can find more informati

Mikk Mihkel Nurges 1.9k Dec 31, 2022
A framework for serving GraphQL from Laravel

Lighthouse A framework for serving GraphQL from Laravel Lighthouse is a GraphQL framework that integrates with your Laravel application. It takes the

NuWave Commerce 3.1k Jan 6, 2023
EXPERIMENTAL plugin extending WPGraphQL to support querying (Gutenberg) Blocks as data, using Server Side Block registries to map Blocks to the GraphQL Schema.

WPGraphQL Block Editor This is an experimental plugin to work toward compatiblity between the WordPress Gutenberg Block Editor and WPGraphQL, based on

WPGraphQL 29 Nov 18, 2022
🍞🧑‍🍳 An on-the-fly GraphQL Schema generator from Eloquent models for Laravel.

An on-the-fly GraphQL Schema generator from Eloquent models for Laravel. Installation Quickstart Model schemas Installation This package requires PHP

Scrn 100 Oct 16, 2022
Add Price Including tax for Magento's "cart" GraphQl query

Comwrap_GraphQlCartPrices Add Price Including tax for Magento's "cart" GraphQl query Query will looks like following: items { id __typenam

Comwrap 1 Dec 2, 2021