Skip to content

Instantly share code, notes, and snippets.

@unclecheese
Last active November 29, 2016 02:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save unclecheese/b56833232f521d9873781a4bc4827a2a to your computer and use it in GitHub Desktop.
Save unclecheese/b56833232f521d9873781a4bc4827a2a to your computer and use it in GitHub Desktop.
<?php
class GraphQLScaffolder
{
const READ
const UPDATE
const DELETE
const CREATE
/**
* @var array $dataObjects
* ex.
* [
* 'member' => [
* 'fields' => [
* 'FirstName', 'Surname', 'Email'
* ],
* 'operations' => [
* 'read' => true,
* 'update' => true
* ],
* 'resolver' => [
* // callable or instanceof GraphQLResolver
* ]
* ],
* 'file' => [
* ...
* ]
* ]
*
*/
protected $dataObjects = [];
public function addDataObject($name)
{
if(dataobject_not_added) {
$this->dataObjects[$name] = [...defaults?]
}
}
public function addOperation($dataObject, $operation)
{
if(operation_is_valid && dataobject_exists) {
$this->dataObjects[$dataObject]['operations'][$operation] = true;
}
}
public function addFieldResolver($dataObject, $resolver)
{
if($resolver instanceof GraphQLResolver || is_callable($resolver)) {
$this->dataObjects[$dataObject]['field_resolver'] = $resolver;
}
}
public function addMutationResolver($dataObject, $mutationType, $resolver)
{
if(($resolver instanceof GraphQLResolver || is_callable($resolver)) && is_mutation($mutationType)) {
$this->dataObjects[$dataObject][$mutationType.'_resolver'] = $resolver;
}
}
public function setFields($dataObject, $fields = [])
{
if(dataobject_exists && is_array($fields) {
$this->dataObjects[$dataObject]['fields'] = $fields;
}
}
public function getTypeCreator($dataObject)
{
return new DataObjectTypeCreator(
$dataObject,
$this->dataObjects[$dataObject]['fields']
);
}
public function getQueryCreator($dataObject)
{
return new DataObjectQueryCreator(
$dataObject,
$this->dataObjects[$dataObject]['field_resolver']
);
}
public function getMutationCreator($dataObject, $mutation)
{
return new DataObjectMutationCreator(
$dataObject,
$mutation,
$this->dataObjects[$dataObject][$mutation.'_resolver']
);
}
public function addToManager(Manager $manager)
{
foreach($this->dataObjects as $dataObjectName => $config) {
$manager->addType(
$this->getTypeCreator($dataObjectName)->getType(),
$type
);
if($config['operations'][self::READ]) {
$manager->addQuery(
$this->getQueryCreator($dataObjectName)->toArray(),
"read".ucfirst($type)
);
}
foreach([self::UPDATE, self::DELETE] as $mutation) {
if($config['operations'][$mutation]) {
$manager->addMutation(
$this->getMutationCreator($dataObjectName, $mutation)->toArray()
);
}
}
}
$manager->addType($this);
}
}
<?php
class DataObjectTypeCreator extends TypeCreator
{
public function __construct($name, $fields = [])
{
$this->name = $name;
$this->fields = $fields;
}
public function attributes()
{
return [
'name' => $this->name
];
}
public function fields()
{
$fieldMap = [];
foreach($this->fields as $fieldName) {
$graphQLType = $dataObjectInstance
->obj($fieldName)
->getGraphQLType(); // a suite of DBField extensions, maybe?
$fieldMap[$fieldName] = $graphQLType;
}
return $fieldMap;
}
}
<?php
class DataObjectQueryCreator extends QueryCreator
{
public function __construct($dataObject, $resolver) { }
public function attributes()
{
return [
'name' => "read".ucfirst($this->dataObject)
];
}
public function args()
{
// not sure how to deal with this.. search context?
}
public function type()
{
return function() {
return Type::listOf(
$this->manager->getType($this->dataObject)
);
};
}
public function resolve($args)
{
if(is_callable($this->resolver)) {
return $this->resolver($args);
}
if($this->resolver instanceof GraphQLResolver) {
return $this->resolver->resolve($args);
}
return self::config()->default_resolvers['read']->resolve($args);
}
}
<?php
class DataObjectMutationCreator extends MutationCreator
{
public function __construct($dataObject, $mutationType, $resolver) {...}
public function attributes()
{
return [
'name' => $mutationType.ucfirst($this->dataObject)
];
}
public function args()
{
// Input types here?
}
public function type()
{
return function() {
return Type::listOf(
$this->manager->getType($this->dataObject)
);
};
}
public function resolve($object, array $args, $context, $info)
{
if(is_callable($this->resolver)) {
return $this->resolver($args);
}
if($this->resolver instanceof GraphQLResolver) {
return $this->resolver->resolve($args);
}
// e.g. SimpleDeleteResolver, SimpleUpdateResolver
return self::config()->default_resolvers[$this->mutationType]
->resolve($object, $args, $context, $info);
}
}
<?php
// New stuff for the manager class
class Manager
{
public static function createFromConfig($config)
{
/** @var Manager $manager */
$manager = Injector::inst()->create(Manager::class);
if(isset($config['scaffold_providers'])) {
foreach($config['scaffold_providers'] as $provider) {
$scaffolder = $provider->getScaffolder();
$scaffolder->addToManager($manager);
}
}
if(isset($config['scaffolding'])) {
$scaffolder = new GraphQLScaffolder();
foreach($config['scaffolding'] as $dataObjectName => $config) {
$scaffolder->addDataObject($dataObjectName);
$ops = ($config['operations'] === 'all') ? [... all of them] : $config['operations'];
foreach($ops as $op) {
$scaffolder->addOperation($dataObjectName, $op);
}
$scaffolder->setFields($config['fields']);
if(isset($config['field_resolver'])) {
$scaffolder->addFieldResolver($dataObjectName, $config['field_resolver']);
}
if(isset($config['mutation_resolvers'])) {
foreach($config['mutation_resolvers'] as $op => $resolver) {
$scaffolder->addMutationResolver(
$dataObjectName,
$op,
$resolver
);
}
}
}
$scaffolder->addToManager($manager);
}
// ...
}
// ...
}
<?php
// Implementation example: Add some scaffolding to the GraphQL server, procedurally
class MyGraqlQLScaffolder implements GraphQLScaffoldProvider
{
public function getScaffolder() {
return (new GraphQLScaffolder())
->addDataObject('Member')
->addOperation('Member', GraphQLScaffolder::READ, [])
->addFieldResolver('Member', function($args) {
$list = Member::get();
// Optional filtering (could be more succinct through SearchContext)
if($args['Email'])
$list = $list->filter('Email', $args['Email']);
return $list;
})
->addMutationResolver('Member', GraphQLScaffolder::UPDATE, new MySpecialMemberResolver())
->setFields('Group', ['Title']);
}
}
<?php
// Implementation example: a custom resolver
class MySpecialMemberResolver extends GraphQLResolver
{
public function resolve($object, array $args, $context, $info)
{
$object->FirstName = $args['FirstName'];
if($object->FirstName === 'UncleCheese') {
$object->Awesome = true;
}
$object->write();
return $object;
}
}
GraphQL:
# procedural approach
scaffold_providers: [MyGraqlQLScaffolder]
# declarative approach
scaffolding:
Member:
operations:
- all: true # implies 'create', 'read', 'update', 'delete'
fields:
- Email
- Fullname
- Groups # Implicitly creates a "Group" GraphQL type
field_resolver: MyGraphQLMemberResolver
mutation_resolvers:
update: MySpecialMemberResolver
Group:
operations:
- read: true #only allow reads
fields:
Title
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment