I'm using the laravel-graphql library from Folklore. I've followed all the instructions to the T, but when I test it out in the Altair Client for Windows, I get "Unknown Server Error, Code 0". My REST API works so it's either that I made an error in setting up the GraphQL layer or Folklore not updating their library has finally fallen behind to the Laravel version that I'm using. It's 5.7.
POSTTYPE.PHP
<?php
namespace App\GraphQL\Type;
use GraphQL\Type\Definition\Type;
use Folklore\GraphQL\Support\Type as GraphQLType;
/* Here we're giving the type a name and description */
class PostType extends GraphQLType {
protected $attributes = [
'name' => 'Post', //here we're defining the query by giving it a name of Post
'description' => 'One post'
];
public function fields(){
return [
'id' => [
'type' => Type::nonNull(Type::int()),
'description' => 'Primary key; incrementing id of post'
],
'header' => [
'type' => Type::string(),
'description' => 'The header of the post'
],
'body' => [
'type' => Type::string(),
'description' => 'The body of the blog post'
],
'created_at' => [
'type' => Type::string(),
'description' => 'When the post was created'
],
'updated_at' => [
'type' => Type::string(),
'description' => 'When the post was last updated'
],
'img' => [
'type' => Type::string(),
'description' => 'Where the main image of the blog post is stored'
],
];
}
// If you want to resolve the field yourself, you can declare a method
// with the following format resolve[FIELD_NAME]Field()
//protected function resolveEmailField($root, $args)
//{
// return strtolower($root->email);
//}
}
POSTQUERY.PHP
<?php
namespace App\GraphQL\Query;
use GraphQL;
use GraphQL\Type\Definition\Type;
use Folklore\GraphQL\Support\Query;
use App\Post;
class PostsQuery extends Query {
//give the query a name of 'posts'
protected $attributes = [
'name' => 'posts'
];
//define the query type
public function type(){
return Type::listOf(GraphQL::type('Post'));
}
//define things to fetch and turn them into arguments
public function args(){
return [
'id' => ['name' => 'id', 'type' => Type::int()],
'header' => ['name' => 'header', 'type' => Type::string()],
'body' => ['name' => 'body', 'type' => Type::string()],
'img' => ['name' => 'img', 'type' => Type::string()],
];
}
//fetch all the posts
public function resolve($root, $args){
if (isset($args['id'])){
return Post::where('id', $args['id'])->get();
}
else if (isset($args['header'])){
return Post::where('header', $args['header'])->get();
}
else if (isset($args['body'])){
return Post::where('body', $args['body'])->get();
}
else if (isset($args['img'])){
return Post::where('img', $args['img'])->get();
}
else {
return Post::all();
}
}
}
CONFIG/GRAPHQL.PHP
<?php
return [
// The prefix for routes
'prefix' => 'graphql',
// The routes to make GraphQL request. Either a string that will apply
// to both query and mutation or an array containing the key 'query' and/or
// 'mutation' with the according Route
//
// Example:
//
// Same route for both query and mutation
//
// 'routes' => 'path/to/query/{graphql_schema?}',
//
// or define each routes
//
// 'routes' => [
// 'query' => 'query/{graphql_schema?}',
// 'mutation' => 'mutation/{graphql_schema?}'
// ]
//
// you can also disable routes by setting routes to null
//
// 'routes' => null,
//
'routes' => '{graphql_schema?}',
// The controller to use in GraphQL request. Either a string that will apply
// to both query and mutation or an array containing the key 'query' and/or
// 'mutation' with the according Controller and method
//
// Example:
//
// 'controllers' => [
// 'query' => '\Folklore\GraphQL\GraphQLController#query',
// 'mutation' => '\Folklore\GraphQL\GraphQLController#mutation'
// ]
//
'controllers' => '\Folklore\GraphQL\GraphQLController#query',
// Any middleware for the graphql route group
'middleware' => [],
// The name of the default schema used when no argument is provided
// to GraphQL::schema() or when the route is used without the graphql_schema
// parameter.
'schema' => 'default',
// The schemas for query and/or mutation. It expects an array to provide
// both the 'query' fields and the 'mutation' fields. You can also
// provide directly an object GraphQL\Schema
//
// Example:
//
// 'schemas' => [
// 'default' => new Schema($config)
// ]
//
// or
//
// 'schemas' => [
// 'default' => [
// 'query' => [
// 'users' => 'App\GraphQL\Query\UsersQuery'
// ],
// 'mutation' => [
//
// ]
// ]
// ]
//
'schemas' => [
'default' => [
'query' => [
'posts' => 'App\GraphQL\Query\PostsQuery'
],
'mutation' => [
'newPost' => 'App\GraphQL\Mutation\NewPostMutation',
//'updatePostStatus' => App\GraphQL\Mutation\UpdatePostStatusMutation::class,
]
]
],
// The types available in the application. You can then access it from the
// facade like this: GraphQL::type('user')
//
// Example:
//
// 'types' => [
// 'user' => 'App\GraphQL\Type\UserType'
// ]
//
// or whitout specifying a key (it will use the ->name property of your type)
//
'types' => [
'App\GraphQL\Type\PostType',
],
//
//'types' => [
// 'Post' => 'App\GraphQL\Type\PostType',
//],
// This callable will receive every Error object for each error GraphQL catches.
// The method should return an array representing the error.
//
// Typically:
// [
// 'message' => '',
// 'locations' => []
// ]
//
'error_formatter' => ['\Folklore\GraphQL\GraphQL', 'formatError']
];
-
-
UPDATE
So I installed the Rebing library and uninstalled the Folklore one, and got GraphiQL to work. I ran a query in it and got
"message": "Class App\\GraphQL\\Type\\UserType does not exist",
"exception": "ReflectionException",
"file": "C:\\wamp64\\www\\laravel-project\\vendor\\laravel\\framework\\src\\Illuminate\\Container\\Container.php",
"line": 779,
Something tells me I'm not supposed to be seeing paths like this. If this is wrong, how do I fix?
Ok so apparently the Rebing Laravel-GraphQL library (maybe the Folklore one too) handles cache poorly. I had to clear Laravel config cache every time I made a change. So if anyone gets into this problem, ensure you get GraphiQL working and clear your config for each change you make. Nothing is wrong with the double slashes. Apparently it's a UNIX thing. I've never seen that before (or never seen that in a bad error) so forgive me.
In your config/graphql instead of 'posts' => 'App\GraphQL\Query\PostsQuery'
use 'posts' => App\GraphQL\Query\PostsQuery::class.
It depends on version of PHP sometimes.
Related
I followed the following article to setup laravel with graphql on my local machine:
https://auth0.com/blog/developing-and-securing-graphql-apis-with-laravel
I following the complete article step by step without any issue. But when I run my app using
php artisan serve
and hit the endpoint localhost:8000/graphql, I get the following error.
[Sat Aug 31 23:09:20 2019] PHP Fatal error: Declaration of App\GraphQL\Queries\WineQuery::type() must be compatible with
Rebing\GraphQL\Support\Field::type(): GraphQL\Type\Definition\Type
in /Users/sushilsingh/Projects/winestore/app/GraphQL/Queries/WineQuery.php on line 9
Here is my WineQuery.php
<?php
namespace App\GraphQL\Queries;
use App\Wine;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Query;
class WineQuery extends Query
{
protected $attributes = [
'name' => 'wine',
];
public function type()
{
return GraphQL::type('Wine');
}
public function args()
{
return [
'id' => [
'name' => 'id',
'type' => Type::int(),
'rules' => ['required']
],
];
}
public function resolve($root, $args)
{
return Wine::findOrFail($args['id']);
}
}
Here is graphql.php
<?php
declare(strict_types=1);
use example\Type\ExampleType;
use example\Query\ExampleQuery;
use example\Mutation\ExampleMutation;
use example\Type\ExampleRelationType;
return [
// The prefix for routes
'prefix' => 'graphql',
// The routes to make GraphQL request. Either a string that will apply
// to both query and mutation or an array containing the key 'query' and/or
// 'mutation' with the according Route
//
// Example:
//
// Same route for both query and mutation
//
// 'routes' => 'path/to/query/{graphql_schema?}',
//
// or define each route
//
// 'routes' => [
// 'query' => 'query/{graphql_schema?}',
// 'mutation' => 'mutation/{graphql_schema?}',
// ]
//
'routes' => '{graphql_schema?}',
// The controller to use in GraphQL request. Either a string that will apply
// to both query and mutation or an array containing the key 'query' and/or
// 'mutation' with the according Controller and method
//
// Example:
//
// 'controllers' => [
// 'query' => '\Rebing\GraphQL\GraphQLController#query',
// 'mutation' => '\Rebing\GraphQL\GraphQLController#mutation'
// ]
//
'controllers' => \Rebing\GraphQL\GraphQLController::class.'#query',
// Any middleware for the graphql route group
'middleware' => [],
// Additional route group attributes
//
// Example:
//
// 'route_group_attributes' => ['guard' => 'api']
//
'route_group_attributes' => [],
// The name of the default schema used when no argument is provided
// to GraphQL::schema() or when the route is used without the graphql_schema
// parameter.
'default_schema' => 'default',
// The schemas for query and/or mutation. It expects an array of schemas to provide
// both the 'query' fields and the 'mutation' fields.
//
// You can also provide a middleware that will only apply to the given schema
//
// Example:
//
// 'schema' => 'default',
//
// 'schemas' => [
// 'default' => [
// 'query' => [
// 'users' => 'App\GraphQL\Query\UsersQuery'
// ],
// 'mutation' => [
//
// ]
// ],
// 'user' => [
// 'query' => [
// 'profile' => 'App\GraphQL\Query\ProfileQuery'
// ],
// 'mutation' => [
//
// ],
// 'middleware' => ['auth'],
// ],
// 'user/me' => [
// 'query' => [
// 'profile' => 'App\GraphQL\Query\MyProfileQuery'
// ],
// 'mutation' => [
//
// ],
// 'middleware' => ['auth'],
// ],
// ]
//
// 'schemas' => [
// 'default' => [
// 'query' => [
// // 'example_query' => ExampleQuery::class,
// ],
// 'mutation' => [
// // 'example_mutation' => ExampleMutation::class,
// ],
// 'middleware' => [],
// 'method' => ['get', 'post'],
// ],
// ],
'schemas' => [
'default' => [
'query' => [
'wine' => App\GraphQL\Queries\WineQuery::class,
'wines' => App\GraphQL\Queries\WinesQuery::class,
]
],
],
// The types available in the application. You can then access it from the
// facade like this: GraphQL::type('user')
//
// Example:
//
// 'types' => [
// 'user' => 'App\GraphQL\Type\UserType'
// ]
//
'types' => [
// 'example' => ExampleType::class,
// 'relation_example' => ExampleRelationType::class,
// \Rebing\GraphQL\Support\UploadType::class,
'Wine' => App\GraphQL\Types\WineType::class,
],
// The types will be loaded on demand. Default is to load all types on each request
// Can increase performance on schemes with many types
// Presupposes the config type key to match the type class name property
'lazyload_types' => false,
// This callable will be passed the Error object for each errors GraphQL catch.
// The method should return an array representing the error.
// Typically:
// [
// 'message' => '',
// 'locations' => []
// ]
'error_formatter' => ['\Rebing\GraphQL\GraphQL', 'formatError'],
/*
* Custom Error Handling
*
* Expected handler signature is: function (array $errors, callable $formatter): array
*
* The default handler will pass exceptions to laravel Error Handling mechanism
*/
'errors_handler' => ['\Rebing\GraphQL\GraphQL', 'handleErrors'],
// You can set the key, which will be used to retrieve the dynamic variables
'params_key' => 'variables',
/*
* Options to limit the query complexity and depth. See the doc
* # https://github.com/webonyx/graphql-php#security
* for details. Disabled by default.
*/
'security' => [
'query_max_complexity' => null,
'query_max_depth' => null,
'disable_introspection' => false,
],
/*
* You can define your own pagination type.
* Reference \Rebing\GraphQL\Support\PaginationType::class
*/
'pagination_type' => \Rebing\GraphQL\Support\PaginationType::class,
/*
* Config for GraphiQL (see (https://github.com/graphql/graphiql).
*/
'graphiql' => [
'prefix' => '/graphiql',
'controller' => \Rebing\GraphQL\GraphQLController::class.'#graphiql',
'middleware' => [],
'view' => 'graphql::graphiql',
'display' => env('ENABLE_GRAPHIQL', true),
],
/*
* Overrides the default field resolver
* See http://webonyx.github.io/graphql-php/data-fetching/#default-field-resolver
*
* Example:
*
* ```php
* 'defaultFieldResolver' => function ($root, $args, $context, $info) {
* },
* ```
* or
* ```php
* 'defaultFieldResolver' => [SomeKlass::class, 'someMethod'],
* ```
*/
'defaultFieldResolver' => null,
/*
* Any headers that will be added to the response returned by the default controller
*/
'headers' => [],
/*
* Any JSON encoding options when returning a response from the default controller
* See http://php.net/manual/function.json-encode.php for the full list of options
*/
'json_encoding_options' => 0,
];
You missed a few imports and return types, check the updated code:
app/GraphQL/Types/WineType.php
<?php
namespace App\GraphQL\Types;
use App\Wine;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Type as GraphQLType;
class WineType extends GraphQLType
{
protected $attributes = [
'name' => 'Wine',
'description' => 'Details about a wine',
'model' => Wine::class
];
public function fields(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::int()),
'description' => 'Id of the wine',
],
'name' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The name of the wine',
],
'description' => [
'type' => Type::nonNull(Type::string()),
'description' => 'Short description of the wine',
],
'color' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The color of the wine',
],
'grape_variety' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The grape variety of the wine',
],
'country' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The country of origin of the wine',
]
];
}
}
app/GraphQL/Queries/WineQuery.php
<?php
namespace App\GraphQL\Queries;
use App\Wine;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Query;
class WineQuery extends Query
{
protected $attributes = [
'name' => 'wine',
];
public function type(): Type
{
return GraphQL::type('Wine');
}
public function args():array
{
return [
'id' => [
'name' => 'id',
'type' => Type::int(),
'rules' => ['required']
],
];
}
public function resolve($root, $args)
{
return Wine::findOrFail($args['id']);
}
}
app/GraphQL/Queries/WinesQuery.php
<?php
namespace App\GraphQL\Queries;
use App\Wine;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Query;
class WinesQuery extends Query
{
protected $attributes = [
'name' => 'wines',
];
public function type(): Type
{
return Type::listOf(GraphQL::type('Wine'));
}
public function resolve($root, $args)
{
return Wine::all();
}
}
I am writting an application with zf2 and I came to this issue which I don't know how to implement.
At the moment I have a router like this :
'routes' => [
'stock' => [
'type' => 'regex',
'options' => [
'regex' => '/stock(?<sku>\/.*)',
'defaults' => [
'controller' => MyController::class,
'action' => 'check',
],
'spec' => '/path%path%'
],
So when my url contains ../stock/13567/2312 the parameter gets passed into the checkAction function.
However, I would like to show a different content when the url is just ../stock/ or ../stock without any parameter sent. How can I achieve this ?
Thanks.
If you want to show different content depending if sku parameter is passed you can do following thing in your controller:
public function indexAction()
{
// Return sku parameter if exists, false otherwise
$sku = $this->params('sku', false);
if ($sku) {
// For example get single item
...
$view->setTemplate('template-a');
} else {
// Get all items
...
$view->setTemplate('template-b');
}
return $view;
}
Just augment the regex to mark the param as optional, as in the docs:
'regex' => '/stock(?<sku>\/.*)?'
... and don't forget to provide the explicit default value:
'defaults' => [
'controller' => MyController::class,
'action' => 'check',
'sku' => '' // or else
],
I have this behavior in my model:
public function behaviors()
{
return [
'styles' => [
'class' => ImageStyleBehavior::className(),
'path' => \Yii::getAlias('#webroot') . '/files/userphotos/styles',
'url' => \Yii::getAlias('#web') . '/files/userphotos/styles',
'attribute' => 'photo',
'styles' => [
'300x300' => [$this, 'style300'], //can be any valid callable
'100x100' => [$this, 'style100'], //can be any valid callable
]
]
];
}
The photo have default value of noavatar.png, and when I try to insert, I get this error:
Exception 'Imagine\Exception\RuntimeException' with message 'Unable to open image /var/www/c2c/Care2Shine/www/files/userphotos/'
Is there a way for me to prevent behavior on insert actions?
You can remove specific named behaviors by detaching them:
$model->detachBehavior('styles');
Or, if it's the only behavior, you can just detach all:
$model->detachBehaviors();
To ensure you only detach on insert, check isNewRecord property.
Does the ImageStyleBehavior extend the AttributeBehavior ? in that case you should be able to use:
public function behaviors()
{
return [
[
'class' => AttributeBehavior::className(),
'attributes' => [
ActiveRecord::EVENT_BEFORE_INSERT => 'attribute1',
ActiveRecord::EVENT_BEFORE_UPDATE => 'attribute2',
],
'value' => function ($event) {
return 'some value';
},
],
];
}
In ZF2, I get the form from the controller factory like this:
class SomeControllerFactory implements FactoryInterface
{
public function CreateService(SeviceLocatorInterface $serviceLocator)
{
$realServiceLocator = $serviceLocator->getServiceLocator();
// other things from service manager
$registrationForm = $realServiceLocator->get('FormElementManager')
->get('Path\To\My\Form\RegistrationForm');
}
return new SomeController(
// controller dependencies, including $registrationForm
);
}
In the RegistrationForm, I have MultiCheckBox:
$this->add([
'type' => 'Zend\Form\Element\MultiCheckBox',
'name' => 'partyRoleIds',
'options' => [
'label' => 'Отношение',
'value_options' => [
[
'value' => '1',
'label' => 'client',
],
[
'value' => '2',
'label' => 'prospect'],
[
'value' => '6',
'label' => 'contractor',
],
],
],
]);
I want to populate value_options from a db query that returns an array like [1 => 'client', 2 => 'prospect'...]. Populating is not an problem, but I don't know how to pass this array as a dependency into the RegistrationForm because in the call $registrationForm = $realServiceLocator->get('FormElementManager')->get('Path\To\My\Form\RegistrationForm');, I don't have any place to add the dependency.
How could I do this?
PS: rewritten the question, please forgive my initial brevity.
In form classes you add the method :
public function setValueOptions($element, array $values_options)
{
$e = $this->get($element);
$e->setValueOptions($values_options);
return $this;
}
In your controller, if your form is $registrationForm you write :
$registrationForm->setValueOptions('partyRoleIds', $valueOptions);
where $valueOptions is an array like your sample.
I am using php's client library for elasticsearch. I'd like to create an index that indexes a person's id and his name, and allows the user to search for names in a very flexible way (case insensitive, search for partial names, etc.
Here is a code snippet of what I have so far, annotated with comments for convenience
<?php
require_once(__DIR__ . '/../init.php');
$client = new Elasticsearch\Client();
$params = [
'index' => 'person',
'body' => [
'settings' => [
// Simple setings for now, single shard
'number_of_shards' => 1,
'number_of_replicas' => 0,
'analysis' => [
'filter' => [
'shingle' => [
'type' => 'shingle'
]
],
'analyzer' => [
'my_ngram_analyzer' => [
'tokenizer' => 'my_ngram_tokenizer',
]
],
// Allow searching for partial names with nGram
'tokenizer' => [
'my_ngram_tokenizer' => [
'type' => 'nGram',
'min_gram' => 1,
'max_gram' => 15,
'token_chars' => ['letter', 'digit']
]
]
]
],
'mappings' => [
'_default_' => [
'properties' => [
'person_id' => [
'type' => 'string',
'index' => 'not_analyzed',
],
// The name of the person
'value' => [
'type' => 'string',
'analyzer' => 'my_ngram_analyzer',
'term_vector' => 'yes',
'copy_to' => 'combined'
],
]
],
]
]
];
// Create index `person` with ngram indexing
$client->indices()->create($params);
// Index a single person using this indexing scheme
$params = array();
$params['body'] = array('person_id' => '1234', 'value' => 'Johnny Appleseed');
$params['index'] = 'person';
$params['type'] = 'type';
$params['id'] = 'id';
$ret = $client->index($params);
// Get that document (to prove it's in there)
$getParams = array();
$getParams['index'] = 'person';
$getParams['type'] = 'type';
$getParams['id'] = 'id';
$retDoc = $client->get($getParams);
print_r($retDoc); // success
// Search for that document
$searchParams['index'] = 'person';
$searchParams['type'] = 'type';
$searchParams['body']['query']['match']['value'] = 'J';
$queryResponse = $client->search($searchParams);
print_r($queryResponse); // FAILURE
// blow away index so that we can run the script again immediately
$deleteParams = array();
$deleteParams['index'] = 'person';
$retDelete = $client->indices()->delete($deleteParams);
I have had this search feature working at times, but I've been fussing with the script to get the case insensitive feature working as expected, and in the process, the script now fails to find any person with a J or j used as the query value to match.
Any ideas what might be going on here?
To fix the case insensitive bit, I added
'filter' => 'lowercase',
to my ngram analyzer.
Also, the reason why it was failing to begin with is that, while using php's client library, you can't create the index then search it in the same script. My guess is something async is going on here. So create the index in one script and search it in another script, it should work.