Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEATURE: DataObject scaffolding #20

Merged
merged 66 commits into from
Feb 2, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
38535d8
FEATURE: DataObject scaffolding
Dec 12, 2016
ab44175
Fix phpunit.xml syntax
Dec 15, 2016
3b73495
README updates for scaffolding docs
Dec 15, 2016
81e0809
Remove @package declarations
Dec 15, 2016
51c0781
Change $dataObjectName to $dataObjectClass
Dec 15, 2016
a43d143
Type hinting
Dec 15, 2016
eaa4982
Updates to arg creation and resolver logic to all CRUD operations
Dec 15, 2016
68306e7
Add type detection to DataObject getters that are not casted
Dec 15, 2016
2c6379a
Remove @package declaration
Dec 15, 2016
5d061e7
Use new required resolver argument
Dec 15, 2016
ddb0781
More informative exception message
Dec 15, 2016
349cb17
New hasType() method for Manager
Dec 16, 2016
eb1ed99
Provide pagination in scaffolded queries
Dec 18, 2016
e353462
Cast default value for args
Dec 18, 2016
54de8f7
API changes to put queries and mutations at top level, nested query s…
Dec 20, 2016
a47421a
Remove creators, major reorganisation
Dec 20, 2016
327f60c
Update readme with new scaffolding api
Dec 23, 2016
cd2956c
Expose nested queries through public api
Jan 9, 2017
7cec89a
Move operation() method, allow removal by identifier
Jan 9, 2017
8068dd9
Allow allFieldsExcept to take a string
Jan 9, 2017
11c4c44
Move operation() method
Jan 9, 2017
7ccb104
Omit data object class from ancestry
Jan 9, 2017
ee2798a
Allow fieldsExcept or fields to be defined in config, not just fields
Jan 9, 2017
566623c
Bugfix: missing use statement
Jan 9, 2017
beafd24
Bugfix: missing use statement
Jan 9, 2017
11d4ce4
New public api, allowing find and remove by identifier
Jan 9, 2017
253065f
Change conditional block to not return too early
Jan 11, 2017
6e4d784
Change "scaffolds" to "types"
Jan 11, 2017
7a83af4
Use injector get instead of create
Jan 11, 2017
f5b0a38
Remove debug
Jan 11, 2017
b241d0d
Expose args to public api
Jan 11, 2017
fb5cd0d
Add new tests
Jan 11, 2017
554d5e8
format PSR2
Jan 11, 2017
d69a482
rename graphqlscaffolder to schemascaffolder
Jan 11, 2017
c4d9544
namespace updates to README
Jan 11, 2017
5f65e50
README updates
Jan 11, 2017
a823b20
Use non-matching group for type parser
Jan 12, 2017
28d8fd9
More README updates
Jan 12, 2017
a5dbd75
Add can* checks to examples
Jan 12, 2017
5ff2f6b
Add can* checks to CRUD using $context
Jan 12, 2017
69dbe7b
Add input type examples
Jan 12, 2017
5aa0310
replace member use statement
Jan 12, 2017
7daee8e
Add tests for CRUD operations
Jan 13, 2017
f6a3cc3
New addFields API, allow descriptions wildcard in yaml
Jan 14, 2017
46c1967
New argument API. Descriptions/defaults allowed
Jan 17, 2017
36302fe
Update example code
Jan 20, 2017
f49759c
BUGFIX: incorrect array shape for CRUD args
Jan 20, 2017
aeed6c7
BUGFIX: Malformed input type fields
Jan 20, 2017
4716387
PSR2 formatting
Jan 31, 2017
62cdf22
Code formatting for tests
Jan 31, 2017
d018593
Clean up rebasing issues
Jan 31, 2017
1066eb7
docblock revisions
Jan 31, 2017
7672e24
Rename Configurable to ConfigurationApplier
Jan 31, 2017
1977943
More docblock cleanup
Jan 31, 2017
d6eb733
Update example code to use DateTime
Jan 31, 2017
8296a23
Add new fake redirector page so tests pass without CMS
Jan 31, 2017
1456d5b
Build shim classes for Page, SiteTree
Jan 31, 2017
17dc73e
API Separate connection() to get/create
chillu Feb 1, 2017
c08bda7
Use DB transaction for multi delete
chillu Feb 2, 2017
06ff0b2
Fix examples
chillu Feb 2, 2017
198127c
Fix example $db declaration
chillu Feb 2, 2017
641213b
Fix example whitespace
chillu Feb 2, 2017
7047fba
Use DO->update() for create
chillu Feb 1, 2017
3e981b7
Use DataObjectSchema
chillu Feb 2, 2017
7d41007
More example class reference fixes
chillu Feb 2, 2017
fd5d41f
Correct use of currentUser context
chillu Feb 2, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
884 changes: 881 additions & 3 deletions README.md

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions _config/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
Name: graphqlconfig
---
SilverStripe\GraphQL\Scaffolding\Scaffolders\DataObjectScaffolder:
default_fields:
ID: ID

## Map DB fields to GraphQL types
SilverStripe\ORM\FieldType\DBField:
# fallback
graphql_type: 'String'

SilverStripe\ORM\FieldType\DBInt:
graphql_type: 'Int'

SilverStripe\ORM\FieldType\DBBoolean:
graphql_type: 'Boolean'

SilverStripe\ORM\FieldType\DBFloat:
graphql_type: 'Float'

SilverStripe\ORM\FieldType\DBPrimaryKey:
graphql_type: 'ID'

SilverStripe\ORM\FieldType\DBForeignKey:
graphql_type: 'ID'
47 changes: 45 additions & 2 deletions examples/_config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,48 @@ SilverStripe\GraphQL:
createMember: 'MyProject\GraphQL\CreateMemberMutationCreator'
# Enforce HTTP basic authentication for GraphQL requests
authenticators:
- class: SilverStripe\GraphQL\Auth\BasicAuthAuthenticator
priority: 10
class: SilverStripe\GraphQL\Auth\BasicAuthAuthenticator
priority: 10
scaffolding_providers:
- My\Project\Post
scaffolding:
types:
My\Project\Post:
fields: [ID, Title, Content, Author, Date]
nestedQueries:
Comments:
args:
Today: Boolean
sortableFields: [Author]
resolver: My\Project\CommentsResolver
Files: true
operations:
create: true
read:
args:
StartingWith: String
resolver: My\Project\ReadResolver
SilverStripe\Security\Member:
fields: [Name, FirstName, Surname, Email]
SilverStripe\Assets\File:
fieldsExcept: [Content]
fields: [File]
My\Project\Comment:
fields: [Comment, Author]
SilverStripe\CMS\Model\RedirectorPage:
fields: [ExternalURL, Content]
operations:
read: true
create: true
mutations:
updatePostTitle:
type: My\Project\Post
args:
ID: ID!
NewTitle: String!
resolver: My\Project\UpdatePostResolver
queries:
latestPost:
type: My\Project\Post
paginate: false
resolver: My\Project\LatestPostResolver
23 changes: 23 additions & 0 deletions examples/code/Comment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace MyProject;

use SilverStripe\ORM\DataObject;

class Comment extends DataObject
{
private static $db = [
'Comment' => 'Text',
'Author' => 'Varchar'
];

private static $has_one = [
'Post' => Post::class
];

public function canView($member = null, $context = []) { return true; }
public function canEdit($member = null, $context = []) { return true; }
public function canCreate($member = null, $context = []) { return true; }
public function canDelete($member = null, $context = []) { return true; }

}
20 changes: 20 additions & 0 deletions examples/code/CommentsResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace MyProject;

use SilverStripe\GraphQL\Scaffolding\Interfaces\ResolverInterface;

class CommentsResolver implements ResolverInterface
{
public function resolve($object, $args, $context, $info)
{
$comments = $object->Comments();

if(isset($args['Today']) && $args['Today']) {
$comments = $comments->where('DATE(Created) = DATE(NOW())');
}

return $comments;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Daaaaaamn! That's much easier to digest!


}
13 changes: 13 additions & 0 deletions examples/code/LatestPostResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace MyProject;

use SilverStripe\GraphQL\Scaffolding\Interfaces\ResolverInterface;

class LatestPostResolver implements ResolverInterface
{
public function resolve($object, $args, $context, $info)
{
return Post::get()->sort('Date', 'DESC')->first();
}
}
2 changes: 1 addition & 1 deletion examples/code/PaginatedReadMembersQueryCreator.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

class PaginatedReadMembersQueryCreator extends PaginatedQueryCreator
{
public function connection()
public function createConnection()
{
return Connection::create('paginatedReadMembers')
->setConnectionType(function () {
Expand Down
141 changes: 141 additions & 0 deletions examples/code/Post.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
<?php

namespace MyProject;

use SilverStripe\ORM\DataObject;
use SilverStripe\GraphQL\Scaffolding\Interfaces\ScaffoldingProvider;
use SilverStripe\GraphQL\Scaffolding\Scaffolders\SchemaScaffolder;
use SilverStripe\Security\Member;
use SilverStripe\Assets\File;

class Post extends DataObject implements ScaffoldingProvider
{

private static $db = [
'Title' => 'Varchar',
'Content' => 'HTMLText',
'Date' => 'Datetime'
];

private static $has_one = [
'Author' => Member::class
];

private static $many_many = [
'Files' => File::class
];

private static $has_many = [
'Comments' => Comment::class
];

public function provideGraphQLScaffolding(SchemaScaffolder $scaffolder)
{
$scaffolder
->type(Post::class)
->addFields(['ID', 'Title', 'Content', 'Author', 'Date'])
// basic many_many nested query, no options
->nestedQuery('Files')
->end()
// more complex nested query
->nestedQuery('Comments')
->addArgs([
'Today' => 'Boolean'
])
->addSortableFields(['Author'])
->setResolver(function ($obj, $args, $context) {
$comments = $obj->Comments();
if (isset($args['Today']) && $args['Today']) {
$comments = $comments->where('DATE(Created) = DATE(NOW())');
}

return $comments;
})
->end()
// basic crud operation, no options
->operation(SchemaScaffolder::CREATE)
->end()
// complex crud operation, with custom args
->operation(SchemaScaffolder::READ)
->addArgs([
'StartingWith' => 'String'
])
->setResolver(function ($obj, $args) {
$list = Post::get();
if (isset($args['StartingWith'])) {
$list = $list->filter('Title:StartsWith', $args['StartingWith']);
}

return $list;
})
->end()
->end()
// these types were all created implicitly above. Add some fields to them.
->type(Member::class)
->addFields(['Name', 'FirstName', 'Surname', 'Email'])
->end()
->type(File::class)
->addAllFieldsExcept(['Content'])
->addFields(['File'])
->end()
->type(Comment::class)
->addFields(['Comment', 'Author'])
->end()
// Arbitrary mutation
->mutation('updatePostTitle', Post::class)
->addArgs([
'ID' => 'ID!',
'NewTitle' => 'String!'
])
->setResolver(function ($obj, $args) {
$post = Post::get()->byID($args['ID']);
if ($post->canEdit()) {
$post->Title = $args['NewTitle'];
$post->write();
}

return $post;
})
->end()
// Arbitrary query
->query('latestPost', Post::class)
->setUsePagination(false)
->setResolver(function ($obj, $args) {
return Post::get()->sort('Date', 'DESC')->first();
})
->end()
->type('SilverStripe\CMS\Model\RedirectorPage')
->addFields(['ExternalURL', 'Content'])
->operation(SchemaScaffolder::READ)
->end()
->operation(SchemaScaffolder::CREATE)
->end()
->end()
->type('Page')
->addFields(['BackwardsTitle'])
->end();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to have this indented to retain some sanity ;)



return $scaffolder;
}

public function canView($member = null, $context = [])
{
return true;
}

public function canEdit($member = null, $context = [])
{
return true;
}

public function canCreate($member = null, $context = [])
{
return true;
}

public function canDelete($member = null, $context = [])
{
return true;
}
}
20 changes: 20 additions & 0 deletions examples/code/ReadResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace MyProject;

use SilverStripe\GraphQL\Scaffolding\Interfaces\ResolverInterface;

class ReadResolver implements ResolverInterface
{
public function resolve($object, $args, $context, $info)
{
$list = Post::get();

if(isset($args['Title'])) {
$list = $list->filter('Title:PartialMatch', $args['Title']);
}

return $list;
}

}
21 changes: 21 additions & 0 deletions examples/code/UpdatePostResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace MyProject;

use SilverStripe\GraphQL\Scaffolding\Interfaces\ResolverInterface;

class UpdatePostResolver implements ResolverInterface
{
public function resolve($object, $args, $context, $info)
{
$post = Post::get()->byID($args['ID']);

if($post->canEdit()) {
$post->Title = $args['NewTitle'];
$post->write();
}

return $post;
}

}
2 changes: 1 addition & 1 deletion src/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public function index(HTTPRequest $request)
}
$contentType = $request->getHeader('Content-Type') ?: $request->getHeader('content-type');
$isJson = preg_match('#^application/json\b#', $contentType);
if ($isJson) {
if ($isJson) {
$rawBody = $request->getBody();
$data = json_decode($rawBody ?: '', true);
$query = isset($data['query']) ? $data['query'] : null;
Expand Down
Loading