Skip to content

Commit

Permalink
API: Introduce FormMutation and FormSubmission (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
wilr committed Nov 29, 2016
1 parent 431ec6a commit 6d20b78
Show file tree
Hide file tree
Showing 8 changed files with 383 additions and 1 deletion.
7 changes: 7 additions & 0 deletions _config/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
SilverStripe\GraphQL:
schema:
types:
fieldSubmission: 'SilverStripe\GraphQL\Forms\FieldSubmissionTypeCreator'
formResult: 'SilverStripe\GraphQL\Forms\FormResultTypeCreator'
mutations:
submitForm: 'SilverStripe\GraphQL\Forms\FormMutationCreator'
50 changes: 50 additions & 0 deletions src/JsonType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace SilverStripe\GraphQL;

use SilverStripe\Core\Object;
use GraphQL\Type\Definition\ScalarType;
use SilverStripe\GraphQL\Manager;

/**
* A custom {@link ScalarType} to represent arbitrary JSON based back to the
* client.
*/

class JsonType extends ScalarType {

/**
* @var string $name
*/
public $name = 'JsonType';


public function __construct() {
parent::__construct();
}

/**
* @param array $value
*
* @return string
*/
public function serialize($value) {
return json_encode($value);
}

/**
* @param array $value
*
* @return string
*/
public function parseValue($value) {
return $value;
}

/**
* @param GraphQL\Language\AST\Value
*/
public function parseLiteral($valueAST) {
return $valueAST;
}
}
2 changes: 1 addition & 1 deletion src/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ public function addType(Type $type, $name = '')
*/
public function getType($name)
{
return $this->types[$name];
return (isset($this->types[$name])) ? $this->types[$name] : null;
}

/**
Expand Down
52 changes: 52 additions & 0 deletions src/forms/FieldSubmissionTypeCreator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace SilverStripe\GraphQL\Forms;

use GraphQL\Type\Definition\Type;
use SilverStripe\GraphQL\TypeCreator;

/**
* Represents a single field submission with a name and value within a form.
*
* Form values are not strictly typed. They will be resolved as strings.
*
* @todo strictly type input?
* @todo how would Form array submit?
*/
class FieldSubmissionTypeCreator extends TypeCreator
{
/**
* Use on input of the form. GraphQL will not return a fieldSubmission but
* it will use it for the input.
*
* @var boolean
*/
protected $inputObject = true;

/**
* @return array
*/
public function attributes()
{
return [
'name' => 'fieldSubmission',
];
}

/**
* @return array
*/
public function fields()
{
return [
'name' => [
'type' => Type::nonNull(Type::string()),
'description' => 'Form field name'
],
'value' => [
'type' => Type::string(),
'description' => 'Form field value'
],
];
}
}
127 changes: 127 additions & 0 deletions src/forms/FormMutationCreator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php

namespace SilverStripe\GraphQL\Forms;

use SilverStripe\GraphQL\MutationCreator;
use SilverStripe\Core\Injector\Injector;
use GraphQL\Type\Definition\Type;

// temp work around
use SilverStripe\AssetAdmin\Controller\AssetAdmin;

/**
* This GraphQL mutation handles submissions from a form with a given URL and
* delegates the handling of the form to the specific {@link Form}.
*
* A GraphQL submission outline looks like the following:
*
* <code>
* mutation ($data:[fieldSubmission]!, $className:String!, $action:String!) {
* submitForm(data:$data, className:$className, action: $action) {
* errors
* }
* }
*
* {
* "data": [{name: "Field", value}],
* "className": "SilverStripe\\Form",
* "action": "action_submit"
* }
* </code>
*/
class FormMutationCreator extends MutationCreator {

/**
* @return array
*/
public function attributes()
{
return [
'name' => 'submitForm'
];
}

/**
* The result of a standard mutation via a form is a GraphQL `formResult`.
*
* See {@link FormResultTypeCreator}
*
* @return callable
*/
public function type()
{
return function() {
return $this->manager->getType('formResult');
};
}

/**
* Form submission.
*
* The fully qualified className of the {@link FormFactory} should be passed
* along with the mutation to recreate the form object. Any context required
* for the form to recreate itself should be passed as `data` similar to a
* standard POST request.
*
* @return array
*/
public function args()
{
return [
'data' => [
'type' => Type::listOf($this->manager->getType('fieldSubmission')),
'description' => 'List of fields submitted'
],
'className' => [
'type' => Type::nonNull(Type::string()),
'description' => 'Fully qualified class name for the given form instance'
],
'action' => [
'type' => Type::nonNull(Type::string()),
'description' => 'Name of the FormAction to trigger'
],
];
}

/**
* Handle the form submission.
*
* Initiates a new instance of the {@link Form} record and hands it's off
* to its' resolve function. Note that this process will bypass the
* controller context that the form is operating on so the form has to be
* responsible for permission checking. All context required for the form
* should be passed either as POST data attached to the form or as part of
* the scheme.
*
* Returns a GraphQL `FormResultTypeCreator` type containing any updated
* record keys and the new {@link FormSchema} as the form may change
* behavior with updated data (i.e when saving a page, new fields may be
* added)
*
* @param Object $object
* @param array $args
* @param array $context
* @param array $info
*
* @todo requires silverstripe-framework/issues/6334
*
* @return array
*/
public function resolve($object, array $args, $context, $info)
{
// move the return to the specific form cases
$form = AssetAdmin::create()->getFileEditForm(3);
$schema = Injector::inst()->create('FormSchema');

// requires silverstripe-framework/issues/6334
return [
'messages' => null,
'schema' => $schema->getSchema($form),
'state' => $schema->getState($form),
'errors' => null,
'updatedRecords' => [
'File:3'
]
];
}
}
69 changes: 69 additions & 0 deletions src/forms/FormResultTypeCreator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace SilverStripe\GraphQL\Forms;

use GraphQL\Type\Definition\Type;
use SilverStripe\GraphQL\TypeCreator;
use SilverStripe\GraphQL\JsonType;

/**
* Encapsulates a GraphQL response to a {@link FormSubmissionTypeCreator}.
*
*/
class FormResultTypeCreator extends TypeCreator
{
/**
* @return array
*/
public function attributes()
{
return [
'name' => 'formResult'
];
}

/**
* @return array
*/
public function fields()
{
$json = new JsonType();

return [
'updateQueries' => [
'type' => Type::listOf(Type::string()),
'description' => 'A list of named queries to re-evaluate'
],
'updatedRecords' => [
'type' => Type::listOf(Type::string()),
'description' => 'A list of modified keys for Apollo to fetch.'
],
'addedRecords' => [
'type' => Type::listOf(Type::string()),
'description' => 'A list of added record keys'
],
'deletedRecords' => [
'type' => Type::listOf(Type::string()),
'description' => 'A list of deleted record keys'
],
'schema' => [
'type' => $json,
'description' => 'Updated form schema'
],
'state' => [
'type' => $json,
'description' => 'Updated form state'
],
'errors' => [
'type' => Type::listOf(Type::string()),
'description' => 'Error messages'
],
'messages' => [
'type' => Type::listOf(Type::string()),
'description' => 'Success message'
],
];
}
}


26 changes: 26 additions & 0 deletions tests/Fake/FormFake.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace SilverStripe\GraphQL\Tests;

use SilverStripe\Dev\TestOnly;
use SilverStripe\ORM\DataObject;

class FormFake extends Form implements TestOnly
{
public function Fields()
{
return new FieldList(
new TextField('Name'),
new EmailField('Email')
);
}

public function getAllActions()
{
return new FieldList(
FormAction::create('submit', 'Submit')->submit(function() {
// @todo, requires a core framework change
})
);
}
}
51 changes: 51 additions & 0 deletions tests/FormTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace SilverStripe\Tests\GraphQL;

use SilverStripe\Control\HTTPRequest;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\GraphQL\Manager;
use SilverStripe\GraphQL\Controller;
use SilverStripe\GraphQL\Tests\Fake\FormFake;
use SilverStripe\Core\Config\Config;
use GraphQL\Schema;
use GraphQL\Type\Definition\ObjectType;
use ReflectionClass;
use Exception;

class FormTest extends SapphireTest
{
public function testFormSubmission()
{
$controller = new Controller();
$manager = new Manager();

$controller->setManager($manager);
$response = $controller->index(new HTTPRequest('POST', '', '', [
'operationName' => 'SubmitForm',
'query' => '
mutation SubmitForm($data: [fieldSubmission]!, $className: String!, $action: String!) {
submitForm(data: $data, className: $className, action: $action) {
messages
updateQueries
updatedRecords
addedRecords
deletedRecords
scheme
errors
messages
}
}',
'variables' => [
'action' => 'submit',
'className' => 'SilverStripe\\GraphQL\\Tests\\Fake\\FormFake',
'data' => [
'name' => 'Name',
'email' => '[email protected]'
]
]
]));

$this->assertFalse($response->isError());
}
}

0 comments on commit 6d20b78

Please sign in to comment.