GraphQL implementation with power of Laravel

Overview

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 the GraphQL Introduction on the React blog or you can read the GraphQL specifications.

Latest Stable Version Latest Unstable Version Total Downloads Monthly Downloads Daily Downloads License Build Status

Installation

composer require studio-net/laravel-graphql @dev

If you're not using Laravel 5.5>=, don't forget to append facade and service provider to you config/app.php file. Next, you have to publish vendor.

php artisan vendor:publish --provider="StudioNet\GraphQL\ServiceProvider"

Usage

Definition

Each source of data must have a corresponding definition in order to retrieve fetchable and mutable fields.

# app/GraphQL/Definition/UserDefinition.php

namespace App\GraphQL\Definition;

use StudioNet\GraphQL\Definition\Type;
use StudioNet\GraphQL\Support\Definition\EloquentDefinition;
use StudioNet\GraphQL\Filter\EqualsOrContainsFilter;
use App\User;
use Auth;

/**
 * Specify user GraphQL definition
 *
 * @see EloquentDefinition
 */
class UserDefinition extends EloquentDefinition {
	/**
	 * Set a name to the definition. The name will be lowercase in order to
	 * retrieve it with `\GraphQL::type` or `\GraphQL::listOf` methods
	 *
	 * @return string
	 */
	public function getName() {
		return 'User';
	}

	/**
	 * Set a description to the definition
	 *
	 * @return string
	 */
	public function getDescription() {
		return 'Represents a User';
	}

	/**
	 * Represents the source of the data. Here, Eloquent model
	 *
	 * @return string
	 */
	public function getSource() {
		return User::class;
	}

	/**
	 * Which fields are queryable ?
	 *
	 * @return array
	 */
	public function getFetchable() {
		return [
			'id'          => Type::id(),
			'name'        => Type::string(),
			'last_login'  => Type::datetime(),
			'is_admin'    => Type::bool(),
			'permissions' => Type::json(),

			// Relationship between user and posts
			'posts'       => \GraphQL::listOf('post')
		];
	}

	/**
	 * Which fields are filterable ? And how ?
	 *
	 * @return array
	 */
	public function getFilterable() {
		return [
			'id'       => new EqualsOrContainsFilter(),
			"nameLike" => function($builder, $value) {
				return $builder->whereRaw('name like ?', $value),
			},
		];
	}

	/**
	 * Resolve field `permissions`
	 *
	 * @param  User $user
	 * @return array
	 */
	public function resolvePermissionsField(User $user) {
		return $user->getPermissions();
	}

	/**
	 * Which fields are mutable ?
	 *
	 * @return array
	 */
	public function getMutable() {
		return [
			'id'          => Type::id(),
			'name'        => Type::string(),
			'is_admin'    => Type::bool(),
			'permissions' => Type::array(),
			'password'    => Type::string()
		];
	}
}

# config/graphql.php

return [
	// ...
	'definitions' => [
		\App\GraphQL\Definition\UserDefinition::class,
		\App\GraphQL\Definition\PostDefinition::class
	],
	// ...
]

The definition is an essential part in the process. It defines queryable and mutable fields. Also, it allows you to apply transformers for only some data with the getTransformers methods. There's 5 kind of transformers to apply on :

  • list : create a query to fetch many objects (User => users)
  • view : create a query to retrieve one object (User => user)
  • drop : create a mutation to delete an object (User => deleteUser)
  • store : create a mutation to update an object (User => user)
  • batch : create a mutation to update many object at once (User => users)
  • restore : create a mutation to restore an object (User => restoreUser)

By the default, the definition abstract class handles Eloquent model transformation.

A definition is composed from types. Our custom class extend the default GraphQL\Type\Definition\Type class in order to implement json and datetime availabled types.

Query

If you want create a query by hand, it's possible.

# app/GraphQL/Query/Viewer.php

namespace App\GraphQL\Query;

use StudioNet\GraphQL\Support\Definition\Query;
use Illuminate\Support\Facades\Auth;
use App\User;
use Auth;

class Viewer extends Query {
	/**
	 * {@inheritDoc}
	 */
	protected function authorize(array $args) {
		// check, that user is not a guest
		return !Auth::guest();
	}

	/**
	 * {@inheritDoc}
	 */
	public function getRelatedType() {
		return \GraphQL::type('user');
	}
	
	/**
	 * {@inheritdoc}
	 */
	public function getSource() {
		return User::class;
	}

	/**
	 * Return logged user
	 *
	 * @return User|null
	 */
	public function getResolver($opts) {
		return Auth::user();
	}
}

# config/graphql.php

return [
	'schema' => [
		'definitions' => [
			'default' => [
				'query' => [
					'viewer' => \App\GraphQL\Query\Viewer::class
				]
			]
		]
	],

	'definitions' => [
		\App\GraphQL\Definition\UserDefinition::class
	]
];

getResolver() receives an array-argument with followed item:

  • root 1st argument given by webonyx library - GraphQL\Executor\Executor::resolveOrError()
  • args 2nd argument given by webonyx library
  • context 3rd argument given by webonyx library
  • info 4th argument given by webonyx library
  • fields array of fields, that were fetched from query. Limited by depth in StudioNet\GraphQL\GraphQL::FIELD_SELECTION_DEPTH
  • with array of relations, that could/should be eager loaded. NOTICE: Finding this relations happens ONLY, if getSource() is defined - this method should return a class name of a associated root-type in query. If getSource() is not defined, then with will be always empty.

Mutation

Mutation are used to update or create data.

# app/GraphQL/Mutation/Profile.php

namespace App\GraphQL\Mutation;

use StudioNet\GraphQL\Support\Definition\Mutation;
use StudioNet\GraphQL\Definition\Type;
use App\User;

class Profile extends Mutation {
	/**
	 * {@inheritDoc}
	 */
	protected function authorize(array $args) {
		// check, that user is not a guest
		return !Auth::guest();
	}

	/**
	 * {@inheritDoc}
	 *
	 * @return ObjectType
	 */
	public function getRelatedType() {
		return \GraphQL::type('user');
	}

	/**
	 * {@inheritDoc}
	 */
	public function getArguments() {
		return [
			'id'      => ['type' => Type::nonNull(Type::id())],
			'blocked' => ['type' => Type::string()]
		];
	};

	/**
	 * Update user
	 *
	 * @param  mixed $root
	 * @param  array $args
	 *
	 * @return User
	 * @SuppressWarnings(PHPMD.UnusedFormalParameter)
	 */
	public function getResolver($root, array $args) {
		$user = User::findOrFail($args['id']);
		$user->update($args);

		return $user;
	}
}

# config/graphql.php

return [
	'schema' => [
		'definitions' => [
			'default' => [
				'query' => [
					'viewer' => \App\GraphQL\Query\Viewer::class
				],
				'mutation' => [
					'viewer' => \App\GraphQL\Mutation\Profile::class
				]
			]
		]
	],

	'definitions' => [
		\App\GraphQL\Definition\UserDefinition::class
	]
];

Pipeline

Pipeline are used to convert a definition into queryable and mutable operations. But, you can easily create your own and manage useful cases like asserting ACL before doing anything, etc.

Pipeline is implemented using the same Laravel Middleware format but pass as first argument the Eloquent Query Builder.

Create new pipe

namespace App/GraphQL/Pipe;

use Closure;
use Illuminate\Database\Eloquent\Builder;

class OnlyAuthored {
	/**
	 * returns only posts that the viewer handle
	 *
	 * @param  Builder $builder
	 * @param  Closure $next
	 * @param  array $opts
	 * @return \Illuminate\Database\Eloquent\Model
	 */
	public function handle(Builder $builder, Closure $next, array $opts) {
		$builder->where('author_id', $opts['context']->getKey());

		return $next($builder);
	}
}
namespace App\GraphQL\Definition;

class PostDefinition extends EloquentDefinition {
	// ...

	/**
	 * {@inheritDoc}
	 *
	 * @return array
	 */
	public function getPipes(): array {
		return array_merge_recursive(parent::getPipes(), [
			'list' => [\App\GraphQL\Pipe\OnlyAuthored::class],
		]);
	}
	
	// ...
}

With this sample, when you'll query posts query, you'll only get viewer posts, not all one. Also, you can specify arguments in the pipe, like following :

namespace App/GraphQL/Pipe;

use Closure;
use Illuminate\Database\Eloquent\Builder;
use GraphQL\Type\Definition\Type;
use StudioNet\GraphQL\Support\Pipe\Argumentable;
use StudioNet\GraphQL\Support\Definition\Definition;

class FilterableGroups implements Argumentable {
	/**
	 * returns only given groups
	 *
	 * @param  Builder $builder
	 * @param  Closure $next
	 * @param  array $opts
	 * @return \Illuminate\Database\Eloquent\Model
	 */
	public function handle(Builder $builder, Closure $next, array $opts) {
		if (array_get($opts, ['args.group_ids', false])) {
			$builder->whereIn('group_id', $opts['args']['group_ids']);
		}

		return $next($builder);
	}

	/**
	 * @implements
	 *
	 * @param  Definition $definition
	 * @return array
	 * @SuppressWarnings(PHPMD.UnusedFormalParameter)
	 */
	public function getArguments(Definition $definition): array {
		return [
			'groups_id' => [
				'type' => Type::json(),
				'description' => 'Filtering by group IDs'
			]
		];
	}
}

Require authorization

Currently you have a possibility to protect your own queries and mutations. You have to implement authorize() method in your query/mutation, that return a boolean, that indicates, if requested query/mutation has to be executed. If method return false, an UNAUTHORIZED GraphQL-Error will be thrown.

Usage examples are in query and mutation above.

Protection of definition transformers are currently not implemented, but may be will in the future. By now you have to define your query/mutation yourself, and protect it then with logic in authorize().

Self documentation

A documentation generator is implemented with the package. By default, you can access it by navigate to /doc/graphql. You can change this behavior within the configuration file. The built-in documentation is implemented from this repository.

Examples

query {
	viewer {
		name
		email

		posts {
			title
			content
		}
	}
}

# is equivalent to (if user id exists)

query {
	user (id: 1) {
		name
		email

		posts {
			title
			content
		}
	}
}

Using filters

When declaring the getFilterable array, you can define filters for fields.

You can either use a closure, an array, or give object of class implementing FilterInterface.

The closure (or the FilterInterface::updateBuilder method) is then called with:

  • $builder : the current laravel query builder
  • $value : the filter value
  • $key : the filter key

You also may define graphql type for you filterable input field. By default Type::json() is used. There are several options to define the type (all examples are listed in following code-block):

  • if you are using class that implements TypedFilterInterface, returned type from method TypedFilterInterface::getType is used;
  • if you are using closure, you have to define an array with keys type containing type you wish and resolver containing closure;
  • if you define an array, and in resolver is passed an object of class with implemented TypedFilterInterface, then type of TypedFilterInterface::getType will overwrite the type in an array key type;
  • in all other situations Type::json() will be used as default type

You can also use the predefined EqualsOrContainsFilter like below.

	public function getFilterable() {
		return [
			// Simple equality check (or "in" if value is an array). Type is Type::json()
			'id'       => new EqualsOrContainsFilter(),
			
			// Customized filter. Type is Type::json()
			"nameLike" => function($builder, $value) {
				return $builder->whereRaw('name like ?', $value);
			},
			
			// type is Type::string()
			"anotherFilter" => [
				"type" => Type::string(),
				"resolver" => function($builder, $value) {
					return $builder->whereRaw('anotherFilter like ?', $value);				
				}
			],
			
			// type is what is returned from `ComplexFilter::getType()`.
			// This is the preffered way to define filters, as it keeps definitions code clean
			"complexFilter" => new ComplexFilter(),
			
			// type in array will be overriden by what is returned from `ComplexFilter::getType()`.
			// this kind of difinition is not clear, but is implemented for backward compatibilities. Please don't use it
			"complexFilter2" => [
				"type" => Type::int(),
				"resolver" => new ComplexFilter()
			],
		];
	}
query {
	users (take: 2, filter: {"id", "1"}) {
		items {
			id
			name
		}
	}
}

This will execute a query : WHERE id = 1

query {
	users (take: 2, filter: {"id", ["1,2"]}) {
		items {
			id
			name
		}
	}
}

This will execute a query : WHERE id in (1,2)

query {
	users (take: 2, filter: {"nameLike", "%santiago%"}) {
		items {
			id
			name
		}
	}
}

This will execute a query : WHERE name like '%santiago%'

Ordering (order_by)

You can specify the order of the results (which calls Eloquent's orderBy) with the order_by argument (which is a String[]).

query {
	users (order_by: ["name"]) { items { id, name } }
}

You can specify a direction by appending asc (which is the default) or desc to the order field :

query {
	users (order_by: ["name_desc"]) { items { id, name } }
}

You can specify multiple order_by :

query {
	users (order_by: ["name_asc", "email_desc"]) { items { id, name } }
}

Pagination : limit (take), offset (skip)

You can limit the number of results with take (Int) :

query {
	users (order_by: ["name"], take: 5) { items { id, name } }
}

You can skip some results with skip (Int) :

query {
	users (order_by: ["name"], take: 5, skip: 10) { items { id, name } }
}

You can get useful pagination information :

query {
	users (order_by: ["name"], take: 5, skip: 10) {
		pagination {
			totalCount
			page
			numPages
			hasNextPage
			hasPreviousPage
		}
		items {
			id
			name
		}
	}
}

Where :

  • totalCount is the total number of results
  • page is the current page (based on take which is used as the page size)
  • numPages is the total number of pages
  • hasNextPage, true if there is a next page
  • hasPreviousPage, true if there is a previous page

Mutation

mutation {
	# Delete object
	delete : deleteUser(id: 5) {
		first_name
		last_name
	},

	# Update object
	update : user(id: 5, with : { first_name : "toto" }) {
		id
		first_name
		last_name
	},

	# Create object
	create : user(with : { first_name : "toto", last_name : "blabla" }) {
		id
		first_name
		last_name
	},

	# Update or create many objects at once
	batch  : users(objects: [{with: {first_name: 'studio'}}, {with: {first_name: 'net'}}]) {
		id
		first_name
	}
}

Mutation: custom input fields

You can specify a "mutable" field which is not in the Eloquent Model, and define a custom method to it.

For a field named foo_bar, the method has to be named inputFooBarField, and it has the Eloquent Model and the user input value as arguments.

Exemple (in Definition) :

	use Illuminate\Database\Eloquent\Model;

	/* ... */

	public function getMutable() {
		return [
			'id' => Type::id(),
			'name' => Type::string(),
			// ...
			// Define a custom input field, which will uppercase the value
			'name_uppercase' => Type::string(),
		];
	}

	/* ... */

	/**
	 * Custom input field for name_uppercase
	 *
	 * @param Model $model
	 * @param string $value
	 */
	public function inputNameUppercaseField(Model $model, $value) {
		$model->name = mb_strtoupper($value);
	}

The input method is executed before the model is saved.

You can return an array with a "saved" callback, which will be executed post-save (which can be useful for eloquent relational models) :

	/**
	 * Custom input field for name_uppercase
	 *
	 * @param Model $model
	 * @param string $value
	 */
	public function inputNameUppercaseField(Model $model, $value) {
		$model->name = mb_strtoupper($value);

		return [
			'saved' => function() use ($model, $value) {
				// Executed after save
			}
		];
	}

N+1 Problem

The common question is, if graphql library solves n+1 problem. This occures, when graphql resolves relation. Often entities are fetched without relations, and when graphql query needs to fetch relation, for each fetched entity relation would be fetched from SQL separately. So instead of executing 2 SQL queries, you will get N+1 queries, where N is the count of results of root entity. In that example you would query only one relation. If you query more relations, then it becomes N^2+1 problem.

To solve it, Eloquent has already options to eager load relations. Transformers in this library use eager loading, depends on what you query.

Currently this smart detection works perfect only on View and List Transformers. Other transformers will be reworked soon.

Contribution

If you want participate to the project, thank you ! In order to work properly, you should install all dev dependencies and run the following commands before pushing in order to prevent bad PR :

$> ./vendor/bin/phpmd src text phpmd.xml
$> ./vendor/bin/phpmd tests text phpmd.xml
$> ./vendor/bin/phpstan analyse --autoload-file=_ide_helper.php --level 1 src
$> ./vendor/bin/php-cs-fixer fix
Comments
  • Fixing n+1 relations problem

    Fixing n+1 relations problem

    Fix for #26

    As you can see, I increased fields depth, if it is a ListTransformer, because one extra depth-level goes for items {...}. Moreover I pick fields from items, and pass them then to guessing relations.

    guessWithRelations() is now recursive and takes 3 params

    • Eloquent model object
    • Fields for the given model, so we can check for relations
    • Parents relation name, as it has to be prepended to currently found relation

    What do you think? As Transformers are currently used just by the lib, it has no breaking changes.

    opened by lorado 13
  • Allow specifying camelCase fields in EloquentDefinitions

    Allow specifying camelCase fields in EloquentDefinitions

    GraphQL style is to camelCase fields, however Eloquent properties are snake_case.

    This PR will look for a camelCase field on an EloquentDefinition by converting it to snake_case before defaulting to null.

    opened by hipsterjazzbo 11
  • Uses pipeline

    Uses pipeline

    The main goal here is to use pipeline instead of modifying transformers :

    class UserDefinition extends Definition {
        /**
         * getTransformers
         *
         * @return void
         */
        public function getTransformers() {
            return [
                'list' => [
                    StudioNet\GraphQL\Pipe\Eloquent\ListPipe::class,
                    App\GraphQL\Pipe\User\RolePipe::class
                ],
                // ...
            ];
        }
    }
    
    // ...
    
    use Illuminate\Database\Eloquent\Builder;
    use Closure;
    
    class RolePipe {
        /**
         * handle
         *
         * @param  Builder $builder
         * @param  Closure $next
         * @param  array $infos
         * @return mixed
         */
        public function handle(Builder $builder, Closure $next, array $infos) {
            // Call $builder method to update SQL query
            //
    	// $infos = [
    	// 	'root'
    	// 	'args'
    	// 	'fields'
    	// 	'context'
    	// 	'info'
    	// 	'with'
    	// 	'source'
    	// 	'rules'
    	// 	'filterables'
    	// 	'definition'
    	// ]
    
            return $next($builder);
    
            // All lines here can modify the collection returned by the next method
        }
    }
    

    This branch will not remove transformers at all, only the underlying code : it will only calls the pipeline and returns the result. To manage custom transformers, a configuration will be set to manage each new transformer (like #22).

    enhancement 
    opened by cmizzi 4
  • Pipes - missing documentation + bug

    Pipes - missing documentation + bug

    It would be nice, if using of pipes in definition would be documented. I also asked you, if it is possible to use pipes for authentication or other type of permission check is possible. You replied with "no", but actually it is possible. By the way, HTTP Middlewares in laravel, like auth middleware, works just the same.

    I thing I got the idea of pipes, it is really nice! I also noticed, that it is generally possible to define pipe in following format: Class\Name\Of\Pipe:argument,another argument. So pipline can parse this string format, and get the class of pipe for creating it from container, and passes to the handle() method some addition parameters, in the example above: argument and another argument as 4th and 5th argument. It works well with pipeline code, but you also use pipes for gql fields generation... and this part of code dont accept this string format. May we fix it? As I would like to add a pipe for checking user role.

    I'll do a merge request, so you can see what I mean... When we merge fix-bug, I can also write a small doc for pipe usage

    opened by lorado 3
  • How to filter relations

    How to filter relations

    Thanks for making this great package. It makes GraphQL even easier! However, I seem to have ran in to a problem. I was hoping I could make a filter function like this in my AlertDefinition:

        /**
         * @return array
         */
        public function getFilterable()
        {
            return [
                'active' => function (Builder $builder, $value) {
                    if (!is_bool($value)) {
                        throw new InvalidArgumentException('Active filter must be of type boolean');
                    }
    
                    return $builder->whereActive($value);
    	    },
            ];
        }
    

    And in my EndpointDefinition I would call the relation like this:

        /**
         * Which fields are queryable ?
         *
         * @return array
         */
        public function getFetchable()
        {
            return [
                'id'     => Type::id(),
                'alerts' => Type::listOf(\GraphQL::type('alert')),
            ];
        }
    

    Now unfortunately, this does not work. The filter will be applied to the root alert query, but it will not allow me to filter like this:

    query {
      endpoints {
       id,
       name,
    
       alerts (filter: {active: true}) {
         id
         created_at
       }
      }
    }
    

    Is there anyway to achieve this "automagically" instead of having to use a custom resolver? Because that would mean that if I would like to apply the same filter on the root alert query, I would either have to write the logic twice, or do some class extending / traits magic which seems rather cumbersome.

    I suppose it would be ideal if something like this would be possible:

        /**
         * Which fields are queryable ?
         *
         * @return array
         */
        public function getFetchable()
        {
            return [
                'id'     => Type::id(),
                'alerts' => AlertDefinition::class,
            ];
        }
    

    (How) can something like this be achieved?

    opened by DiederikvandenB 3
  • N+1 Problem with ListTransformers

    N+1 Problem with ListTransformers

    Hi there! Thank you very much for this awesome package! I am new to GraphQL and tried before a package from rebing, but you package seems to be more fresh and your features are simple crazy awesome!

    When I tried your library, it was important to me, that all relationships loads eager. As you provide transformers, I didn't even need to write a query for testing this out - nice! BUT exactly there I found a bug.

    I would like to fix it, but I don't feel much confidence with your code, as I see at it since 1 hour. So I just describe the problem ;)

    In file StudioNet\GraphQL\Support\Transformer\Transformer.php in method getResolverCallable() you tries to guess the relations with $this->guessWithRelations($definition, $fields). For ViewTransformation (and all other mutation-transformations) it works perfectly. Relations are guessed correctly, as fields at the root contains exactly the structure of queried type.

    But to query data via ListTransformer, we have to use query like this:

    {
       items {...}
    }
    

    So in getResolverCallable() the variable $fields contains that, what was given (same as in example). When you then call $this->guessWithRelations($definition, $fields), variable $fields contains wrong attributes, better saying it contains correct attributes, but inside items{...}. So the guessWithRelations() guesses, there are no relations at all, because it checks only items field.

    I know, you have a TODO there (Improve this checker). If you wish, I could try to improve it.

    P.s. Child-relations are also not guessed.

    opened by lorado 2
  • Is error reporting broken?

    Is error reporting broken?

    When some of my code throws an \Error or \Exception, the error I get back includes a "location", but no indication of where the error occurred — no file, or class name, or trace.

    Is this by design? Could we do something like this in formatError()?

    if (!empty($prev)) {
    	if ($prev instanceof ValidationError) {
    		$error['validation'] = $prev->getValidatorMessages()->toArray();
    	} else if ($prev instanceof \Throwable) {
    	    // Throw in the trace so one can know where an exception occurred
                $error['trace'] = explode("\n", $prev->getTraceAsString());
            }
    }
    

    That would give us a nicely formatted trace back to where the original \Throwable was thrown, so the error is findable (though it should probably be switched based on env(APP_DEBUG))

    opened by hipsterjazzbo 2
  • Adds a search transformer

    Adds a search transformer

    I wanted to add the ability to search with Laravel Scout, but there's not really a way to add custom transformers at the moment at run time, and custom queries were very very verbose.

    This PR adds a transformer to search with Laravel Scout, which is automatically activated when the Eloquent model uses Laravel\Scout\Searchable, similar to SoftDeletes.

    opened by hipsterjazzbo 2
  • Backtick sql bug when using filter transformer?

    Backtick sql bug when using filter transformer?

    When trying to run a query like query={users(filter:{id:"1"}){id,name}} it yields the error in MySql:

    Column not found: 1054 Unknown column 'LOWER(id)' in 'where clause' (SQL: select * from `users` where `LOWER(id)` = 1)"

    shouldn't this be LOWER(`id`)?

    opened by dallincoons 2
  • Improve definition of filterables fields

    Improve definition of filterables fields

    Hi. Here is me again... 🤣

    in my project I wanted to define filterable fields type, but there was no option to do it. So I improved parsing of given values:

    • There is new TypedFilterInterface, with method getType(), that should return wished input type. This type will be used, if instance of class that implements TypedFilterInterface defined in YourDefinition::getFilterables()
    • there is still ability to define input type via array. So with array devinition it is possible to define type and use a closure (see examples below)
    • all possible definitions are tested with unit tests

    So here are all possible definitions of filterables:

    // NOTE: ComplexFilter implements TypedFilterInterface! 
    public function getFilterable() {
    	return [
    		// Simple equality check (or "in" if value is an array). Type is Type::json()
    		'id'       => new EqualsOrContainsFilter(),
    		
    		// Customized filter. Type is Type::json()
    		"nameLike" => function($builder, $value) {
    			return $builder->whereRaw('name like ?', $value);
    		},
    		
    		// type is Type::string()
    		"anotherFilter" => [
    			"type" => Type::string(),
    			"resolver" => function($builder, $value) {
    				return $builder->whereRaw('anotherFilter like ?', $value);				
    			}
    		],
    		
    		// type is what is returned from `ComplexFilter::getType()`.
    		// This is the preffered way to define filters, as it keeps definitions code clean
    		"complexFilter" => new ComplexFilter(),
    		
    		// type in array will be overriden by what is returned from `ComplexFilter::getType()`.
    		// this kind of difinition is not clear, but is implemented for backward compatibilities. Please don't use it
    		"complexFilter2" => [
    			"type" => Type::int(),
    			"resolver" => new ComplexFilter()
    		],
    	];
    }
    

    I also updated README and listed there all possible usages (as above).

    There is NO breaking changes. Old definitions are still working, with default Type::json() type.

    As this is already 4th pull request, after merging some of PRs may require manually merge from me. So... let me know...

    opened by lorado 1
  • Deleting of many2many relations

    Deleting of many2many relations

    Currently I am working a lot with morphed many2many relationships. They gets stored and linked perfectly, but if I want to remove related object, I have to call separately delete mutation for those related objects... For one - it is ok, but when I got many, it gets a little cumbersome.

    So here is an Idea - may be can we add an additional field for those relation inputs, like _delete: boolean, so that unlinked objects will be directly removed?

    For exampe: I have an user with multiple locations (e.g. with IDs 3 and 4). Then I would like to store a user with locations like this:

    mutation {
       user(id: 1, with: {
          locations: [
            {id: 3, _delete: true}, {id: 4, _delete: true}
          ]}
       ) {
          id
       }
    }
    

    So on execution locations with IDs 3 and 4 will be also deleted.

    If it is too strange, then may be can we add ability to remove multiple items with one mutation call on dropTransformer?

    opened by lorado 1
  • Truely nested relation resolvers

    Truely nested relation resolvers

    On defining mutable fields, it is currently possible to simply define which relation should be updated. Input type of this relation is automatically used, as input type of the defined field.

    But what I faced today - for the relations transformation there is pretty simple code, that is not using something from definitions of this relation at all.

    What I was trying: Let's say we have User and Location models. User can have many Locations. Location has a custom input "shape", which could be either a polygon, or simple point with lat/long and radius in meters, for creation circle on the map. On the server side this field is always converted so, that DB stores only polygons.

    If I create Location directly, my custom input works properly. But when I am using nested mutation, it doesn't work, because relations transformation is processed on its own, and has nothing in common with definition.

    So it would be really nice, when on defining custom input in definition, this custom input would be respected also on nested mutations (relations).

    I did a quick look on code, and didn't find any quick solution. So what du you think? As idea - good/bad? And if its good, how would you like to implement it? May be if you could give me some advices, I could try to code it on my own

    opened by lorado 1
  • Feature: file upload

    Feature: file upload

    In my project I decided to upload files via graphql, and not separately with usual controllers. So I did some research and found some helpful links:

    • https://github.com/jaydenseric/graphql-multipart-request-spec
    • https://github.com/Ecodev/graphql-upload
    • https://github.com/ShaneXie/laravel-graphql-upload

    As there is no possibility to add a middleware from outside of this library, I thought it would be cool, if implementation would be in the library directly.

    The implementation of support for multipart request ist pretty simple (thanks to graphql-upload). With adoption to laravel specific classes it works like a charm (thanks to laravel-graphql-upload)

    I currently have 3 PR - there are a little merge conflict (some empty lines were removed/added, and git cant handle it...), so when the stages comes, that master is not compatible with this PR, I'll do a new merge.

    If you have some improvement ideas, let me know

    opened by lorado 0
  • Improving relation transformers

    Improving relation transformers

    For my project I use morphManyToMany a lot. Unfortunately there was not implementation in StoreTransformer to handle such relations. So I decided to implement it.

    Currently I handled just the case of morphManyToMany relation. I also created 3 tests to create, update, clear this type of relation.

    I also seen some improvements potential in other relation transformers. I will probably also refactor them, when I have time. E.g. there is a way to handle morph relations without passing morhType explicitly (currently there is __typename argument for it).

    BTW: It would be nice, when we add some documentation with examples for modifying such relations with store mutation... I should look into test cases, to find HOW graphql should be defined... May be I will do it next week.

    What do you think?

    opened by lorado 0
  • Solving issue #31

    Solving issue #31

    Hi.

    As I already described in my issue #31, almost all helper tools for working with GraphQL claiming at __typename on InputObjects, so I just renamed it to _morphType. With this change it is directly clear, what this field means, and it also don't use any naming reserved by GraphQL anymore.

    Though I have to mention - this PR has brake-changes to some users, if they already used __typename on input.

    opened by lorado 1
  • __typename field on Input types

    __typename field on Input types

    Hi, I just installed a plugin on my PhpStorm: JS GraphQL. It has a great support for working with graphql. When I parsed my schema with this plugin, it threw me an error:

    Error: Name "__typename" must not begin with "__", which is reserved by GraphQL introspection.

    So my question - Is there any sense in appending __typename field of type "string" to all input types for definitions? I didn't see this in other gql schemas before.

    Can we get rid of it? I also didn't find any example for creation Input types with __typename field.

    opened by lorado 4
Releases(v0.9.7)
  • v0.9.7(Mar 14, 2019)

  • v0.9.6(Mar 14, 2019)

    • Improve definition of filterables fields #39 (thx @lorado)
    • Breaking : Forces saving UTC in database, and sending values in timezone from config('app.timezone').

    I hope this will not break things for you, but timezones are so complex - and not managed correctly by laravel.

    Source code(tar.gz)
    Source code(zip)
  • v0.9.5(Nov 29, 2018)

  • v0.9.4(Oct 18, 2018)

  • v0.9.3(Sep 24, 2018)

    • implements exception handler for debug mode purpose. Now, when the APP_DEBUG is set to true, you will see backtrace on every thrown exception. Have a good debugging process ! :joy:
    Source code(tar.gz)
    Source code(zip)
  • v0.8.1(May 29, 2018)

    ⚠ Breaking Change

    Adds pagination support.

    To achieve this, all the lists had to be wrapped in items.

    See https://github.com/studio-net/laravel-graphql#pagination--limit-take-offset-skip

    Source code(tar.gz)
    Source code(zip)
  • v0.7.0(May 21, 2018)

  • v0.6.2(Mar 22, 2018)

  • v0.6.1(Mar 22, 2018)

  • v0.6.0(Mar 19, 2018)

    Refactors how filtering works.

    Breaking changes

    Filtering is being reworked. Filters are now declarative via Definition::getFilterable() (see Readme).

    This is a work in progress.

    Source code(tar.gz)
    Source code(zip)
  • v0.5.4(Dec 6, 2017)

  • v0.5.3(Nov 29, 2017)

  • v0.5.2(Oct 31, 2017)

    • [x] 9f5ac89 - adds definition resolve%sField method ability
    • [x] e7406fb - fixes list transformer argumented fields
    • [x] f647c77 - fixes transformer mutable fields to function
    • [x] 2262dfd - uses GraphQL::executeQuery and toArray() method
    • [x] efcf190 - (composer/master) upgrades webonyx/graphql-php to 0.11.2
    • [x] b614cca - fixes grammar namespace (2017-10-30 18:03:47 +0100)
    Source code(tar.gz)
    Source code(zip)
  • v0.5.1(Oct 6, 2017)

    Bugfixes (BC Break)

    • [x] 39aad22 rewrites unit tests to use GraphQL::execute method instead of HTTP endpoint
    • [x] d4f9418 replaces Type::array() to Type::json() (PHP BC)

    Updates

    • [x] 07bd66c Update README.md
    Source code(tar.gz)
    Source code(zip)
  • 0.5(Oct 4, 2017)

  • v0.4.1(Sep 1, 2017)

    • dff4c1f updates README.md
    • f0a105a adds delete eloquent mutation generator
    • a1371d2 adds laravel auto-register from composer and fixes travis
    • 39d3ca2 performs laravel 5.5 tests
    • 5b2c572 removes Interfaces\ModelAttributes
    • da8f69c supports Laravel 5.{2,3,4}
    • c47a492 removes unused use
    • 6cc8a4e implements Transformer cache system
    • 48946ea implements cache into GraphQL singleton
    • 61a2f39 removes eloquent trait from readme
    • d735b6f adds model attributes unit test
    • 64cddaf replaces EloquentModel traits with cache system
    • 7bd3138 moves EloquentObjectType to Definition\Type
    Source code(tar.gz)
    Source code(zip)
  • v0.4(Aug 8, 2017)

    Bugfixes

    • [x] ca91fdc fixes TypeTransformer

    Features

    • [x] #7 Implements metadata
    • [x] 4d395c0 updates README.md
    • [x] 3a85325 uses Meta type in ModelTransformer
    • [x] 5a660ba creates MetaEloquent generator and registers it
    • [x] d2b46dd appends dependsOn method to generators
    • [x] b7d2600 creates Meta type
    Source code(tar.gz)
    Source code(zip)
  • v0.3(Aug 7, 2017)

  • v0.2-beta(Aug 4, 2017)

    Feature

    • [x] Implement unit test #5
    • [x] Implement Travis CI #5

    Bugfixes

    • [x] fixes model transformer sorter 6f499e9
    • [x] prevent using loadRoutesFrom method (service) 3372a09
    • [x] fixes mutation NodeEloquentGenerator syntax mistake 027b316
    • [x] updates composer.json d939075
    • [x] fixes EloquentModel trait 95427e0
    • [x] fixes EloquentGenerator syntax 4ed964f
    • [x] removes some methods to TypeInterface (handle by transformer) 58aee5a
    • [x] appends cache to EloquentModel in order to improves SQL performances 9ab4b13
    • [x] updates mutation columns resolver a393748
    Source code(tar.gz)
    Source code(zip)
  • v0.1-beta(Aug 2, 2017)

    This version is not able to run in production but exposes basic usage. This version handles :

    • Generators and transformers (#3, #2) ;
    • Eloquent model supports ;
    • Custom type, query and mutation ;
    Source code(tar.gz)
    Source code(zip)
Owner
Studionet
Real Estate Web Agency
Studionet
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
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
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
A PHP port of GraphQL reference implementation

graphql-php This is a PHP implementation of the GraphQL specification based on the reference implementation in JavaScript. Installation Via composer:

Webonyx 4.4k Jan 7, 2023
A Laravel Fractal package for building API responses, giving you the power of Fractal with Laravel's elegancy.

Laravel Responder is a package for building API responses, integrating Fractal into Laravel and Lumen. It can transform your data using transformers,

Alexander Tømmerås 776 Dec 25, 2022
The 1Password Connect PHP SDK provides your PHP applications access to the 1Password Connect API hosted on your infrastructure and leverage the power of 1Password Secrets Automation

1Password Connect PHP SDK The 1Password Connect PHP SDK provides your PHP applications access to the 1Password Connect API hosted on your infrastructu

Michelangelo van Dam 12 Dec 26, 2022
A REST API that should power the Agile Monkeys CRM Service

This is a simple REST API that purposes to power the Agile Monkeys CRM service

Dickens odera 3 Jul 31, 2021
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
🍞🧑‍🍳 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
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
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
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
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
A Statamic Pro addon that provides alternative GraphQL queries for collections, entries and global sets.

Statamic Enhanced GraphQL A Statamic CMS GraphQL Addon that provides alternative GraphQL queries for collections, entries and global sets. ⚠️ This is

Grischa Erbe 2 Dec 7, 2021