-
Notifications
You must be signed in to change notification settings - Fork 61
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
NEW: Schema initialise task #433
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,250 @@ | ||
<?php | ||
|
||
namespace SilverStripe\GraphQL\Dev; | ||
|
||
use SilverStripe\Control\Controller; | ||
use SilverStripe\Control\Director; | ||
use SilverStripe\Control\HTTPRequest; | ||
use SilverStripe\Core\Manifest\ModuleManifest; | ||
use SilverStripe\Core\Path; | ||
use SilverStripe\GraphQL\Schema\Schema; | ||
use SilverStripe\ORM\Connect\NullDatabaseException; | ||
|
||
/** | ||
* A task that initialises a schema with boilerplate config and files. | ||
*/ | ||
class Initialise extends Controller | ||
{ | ||
/** | ||
* @var string[] | ||
*/ | ||
private static $url_handlers = [ | ||
'' => 'initialise' | ||
]; | ||
|
||
/** | ||
* @var string[] | ||
*/ | ||
private static $allowed_actions = [ | ||
'initialise' | ||
]; | ||
|
||
/** | ||
* @var string | ||
*/ | ||
private $appNamespace; | ||
|
||
/** | ||
* @var string | ||
*/ | ||
private $schemaName = 'default'; | ||
|
||
/** | ||
* @var string | ||
*/ | ||
private $graphqlConfigDir = '_graphql'; | ||
|
||
/** | ||
* @var string | ||
*/ | ||
private $graphqlCodeDir = 'GraphQL'; | ||
|
||
/** | ||
* @var string | ||
*/ | ||
private $endpoint = 'graphql'; | ||
|
||
/** | ||
* @var string | ||
*/ | ||
private $projectDir = ''; | ||
|
||
/** | ||
* @var string | ||
*/ | ||
private $srcDir = 'src'; | ||
|
||
/** | ||
* @var string | ||
*/ | ||
private $perms = ''; | ||
|
||
/** | ||
* @param HTTPRequest $request | ||
*/ | ||
public function initialise(HTTPRequest $request) | ||
{ | ||
$isBrowser = !Director::is_cli(); | ||
Schema::invariant( | ||
!$isBrowser, | ||
'This task can only be run from CLI' | ||
); | ||
|
||
if ($request->getVar('help')) { | ||
$this->showHelp(); | ||
return; | ||
} | ||
|
||
$appNamespace = $request->getVar('namespace'); | ||
|
||
if (!$appNamespace) { | ||
echo "Please provide a base namespace for your app, e.g. \"namespace=App\" or \"namespace=MyVendor\MyProject\".\nFor help, run \"dev/graphql/init help=1\"\n"; | ||
return; | ||
} | ||
|
||
$this->appNamespace = $appNamespace; | ||
|
||
$this->projectDir = ModuleManifest::config()->get('project'); | ||
|
||
$schemaName = $request->getVar('name'); | ||
unclecheese marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if ($schemaName) { | ||
$this->schemaName = $schemaName; | ||
} | ||
|
||
$graphqlConfigDir = $request->getVar('graphqlConfigDir'); | ||
if ($graphqlConfigDir) { | ||
$this->graphqlConfigDir = $graphqlConfigDir; | ||
} | ||
|
||
$graphqlCodeDir = $request->getVar('graphqlCodeDir'); | ||
if ($graphqlCodeDir) { | ||
$this->graphqlCodeDir = $graphqlCodeDir; | ||
} | ||
|
||
$endpoint = $request->getVar('endpoint'); | ||
if ($endpoint) { | ||
$this->endpoint = $endpoint; | ||
} | ||
|
||
$srcDir = $request->getVar('srcDir'); | ||
if ($srcDir) { | ||
$this->srcDir = $srcDir; | ||
} | ||
|
||
$absProjectDir = Path::join(BASE_PATH, $this->projectDir); | ||
$this->perms = fileperms($absProjectDir); | ||
|
||
$this->createGraphQLConfig(); | ||
$this->createProjectConfig(); | ||
$this->createResolvers(); | ||
} | ||
|
||
/** | ||
* Creates the graphql schema specific config in _graphql/ | ||
*/ | ||
private function createGraphQLConfig(): void | ||
{ | ||
$absGraphQLDir = Path::join(BASE_PATH, $this->projectDir, $this->graphqlConfigDir); | ||
if (is_dir($absGraphQLDir)) { | ||
echo "Graphql config directory already exists. Skipping." . PHP_EOL; | ||
return; | ||
} | ||
|
||
echo "Creating graphql config directory: $this->graphqlConfigDir" . PHP_EOL; | ||
mkdir($absGraphQLDir, $this->perms); | ||
foreach (['models', 'config', 'types', 'queries', 'mutations'] as $file) { | ||
unclecheese marked this conversation as resolved.
Show resolved
Hide resolved
|
||
touch(Path::join($absGraphQLDir, "$file.yml")); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think creating a bunch of empty files adds much value. I think it's fine having only the config.yml file here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I actually think it does. There's a lot of naming conventions to learn in GraphQL 4, and this just saves a trip to the docs. At a bare minimum, we need There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Having worked with GQL4 I tend to agree that any help in code/prebaked configs is closer to the hands-on dev and therefore better than having to go and read docs to then do the same thing anyway. Even from the perspective of naming the files and what they may contain. |
||
} | ||
$configPath = Path::join($absGraphQLDir, 'config.yml'); | ||
$defaultConfig = <<<YAML | ||
resolvers: | ||
- $this->appNamespace\Resolvers | ||
YAML; | ||
file_put_contents($configPath, $defaultConfig); | ||
} | ||
|
||
/** | ||
* Creates the SS config in _config/graphql.yml | ||
*/ | ||
private function createProjectConfig(): void | ||
{ | ||
$absConfigFile = Path::join(BASE_PATH, $this->projectDir, '_config', 'graphql.yml'); | ||
if (file_exists($absConfigFile)) { | ||
echo "Config file $absConfigFile already exists. Skipping." . PHP_EOL; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be better to suffix the config if the graphql.yml file already exists? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideally, this would do a similar check that the GraphQL devtools controller does, where it finds what schemas are actually routed. That's in a separate module, and it's got some GraphQL 3 backward compat stuff in it. I don't really have much of an appetite for migrating it into GraphQL 4 right now, but we could at some point. Would probably make sense to put in I'm thinking that if you have a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is a good way of handling it - if the file already exists, we inform the developer of that fact and skip it. They can then make a judgement call to either keep the existing schema definition or delete it and run this again. |
||
return; | ||
} | ||
$defaultProjectConfig = <<<YAML | ||
SilverStripe\Control\Director: | ||
rules: | ||
$this->endpoint: '%\$SilverStripe\GraphQL\Controller.$this->schemaName' | ||
SilverStripe\GraphQL\Schema\Schema: | ||
schemas: | ||
$this->schemaName: | ||
src: | ||
- $this->projectDir/$this->graphqlConfigDir | ||
|
||
YAML; | ||
unclecheese marked this conversation as resolved.
Show resolved
Hide resolved
|
||
file_put_contents($absConfigFile, $defaultProjectConfig); | ||
} | ||
|
||
/** | ||
* Creates an example resolvers class for autodiscovery in app/src/GraphQL/Resolvers.php | ||
*/ | ||
private function createResolvers(): void | ||
{ | ||
$absSrcDir = Path::join(BASE_PATH, $this->projectDir, $this->srcDir); | ||
$absGraphQLCodeDir = Path::join($absSrcDir, $this->graphqlCodeDir); | ||
$graphqlNamespace = $this->appNamespace . '\\' . str_replace('/', '\\', $this->graphqlCodeDir); | ||
if (is_dir($absGraphQLCodeDir)) { | ||
echo "GraphQL code dir $this->graphqlCodeDir already exists. Skipping" . PHP_EOL; | ||
return; | ||
} | ||
|
||
echo "Creating resolvers class in $graphqlNamespace" . PHP_EOL; | ||
mkdir($absGraphQLCodeDir, $this->perms, true); | ||
$resolverFile = Path::join($absGraphQLCodeDir, 'Resolvers.php'); | ||
$resolverCode = <<<PHP | ||
<?php | ||
|
||
namespace $graphqlNamespace; | ||
|
||
/** | ||
* Use this class to define custom resolvers. Static functions in this class | ||
* matching the pattern resolve<FieldName> or resolve<TypeNameFieldName> | ||
* will be automatically assigned to their respective fields. | ||
* | ||
* More information: https://docs.silverstripe.org/en/4/developer_guides/graphql/working_with_generic_types/resolver_discovery/#the-resolver-discovery-pattern | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lines too long, will fail PHPCS There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The link itself is enough to break the limit. What would you recommend? |
||
*/ | ||
class Resolvers | ||
{ | ||
public static function resolveMyQuery(\$obj, array \$args, array \$context): array | ||
{ | ||
// Return the result of query { myQuery { ... } } | ||
return []; | ||
} | ||
} | ||
|
||
PHP; | ||
file_put_contents($resolverFile, $resolverCode); | ||
} | ||
|
||
/** | ||
* Outputs help text to the console | ||
*/ | ||
private function showHelp(): void | ||
{ | ||
echo <<<TXT | ||
|
||
**** | ||
This task executes a lot of the boilerplate required to build a new GraphQL schema. It will | ||
generate a few files in your project directory. Any files that already exist will not be | ||
overwritten. The task can be run multiple times and is non-destructive. | ||
**** | ||
|
||
-- Example: | ||
|
||
$ vendor/bin/sake dev/graphql/init namespace="MyAgency\MyApp" | ||
|
||
-- Arguments: | ||
|
||
[namespace]: The root namespace. Required. | ||
|
||
<name>: The name of the schema. Default: "default" | ||
|
||
<graphqlConfigDir>: The folder where the flushless graphql config files will go. Default: "_graphql" | ||
|
||
<graphqlCodeDir>: The subfolder of src/ where your GraphQL code (the resolver class) will go. Follows PSR-4 based on the namespace argument (default: "GraphQL") | ||
|
||
TXT; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any docs that links to
vendor/bin/sake dev/graphql/init
? Right now it's very hiddenThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
silverstripe/silverstripe-framework#10218