diff --git a/README.md b/README.md index 3e5f256..b0712af 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,29 @@ SilverStripe\Control\Director: Schema: default ``` +## GraphQL schema initialise task + +This module provides a `GraphQLSchemaInitTask` task to initialise a basic GraphQL schema to get you started. It will create configuration files for your schema and a basic resolver. Specifically it will create: + +- `app/_config/graphql.yml` +- `app/_graphql` containing several yml files +- `src/GraphQL/Resolvers.php` + +You must be in CLI mode to use this task + +To view help for the task to see what options are available: + +```bash +vendor/bin/sake dev/tasks/GraphQLSchemaInitTask help=1 +``` + +To run the task with with minimal options: + +```bash +vendor/bin/sake dev/tasks/GraphQLSchemaInitTask namespace=App +``` + + ## Upgrading and maintaining the IDE The library running the IDE is [GraphQL Playground](https://github.com/graphql/graphql-playground). diff --git a/src/GraphQLSchemaInitTask.php b/src/GraphQLSchemaInitTask.php new file mode 100644 index 0000000..c074586 --- /dev/null +++ b/src/GraphQLSchemaInitTask.php @@ -0,0 +1,278 @@ +getVar('help')) { + $this->showHelp(); + return; + } + + $appNamespace = $request->getVar('namespace'); + + if (!$appNamespace) { + $segment = static::$segment; + echo "Please provide a base namespace for your app, e.g. \"namespace=App\" or \"namespace=MyVendor\MyProject\".\nFor help, run \"dev/tasks/$segment help=1\"\n"; + return; + } + + $this->appNamespace = $appNamespace; + + $this->projectDir = ModuleManifest::config()->get('project'); + + $schemaName = $request->getVar('name'); + 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 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; + return; + } + + $rulesArr = [ + " $this->endpoint: '%\$SilverStripe\GraphQL\Controller.$this->schemaName'" + ]; + // A default schema is required, even though it's empty + if ($this->schemaName !== 'default') { + $rulesArr[] = " default: '%\$SilverStripe\GraphQL\Controller.default'"; + } + $rules = implode("\n", $rulesArr); + + $extra = ''; + if ($this->schemaName !== 'default') { + $extra = <<schemaName: + class: SilverStripe\GraphQL\Controller + constructor: + schemaKey: $this->schemaName + YAML; + } + + $defaultProjectConfig = <<schemaName: + src: + - $this->projectDir/$this->graphqlConfigDir + + $extra + YAML; + $defaultProjectConfig = trim($defaultProjectConfig) . "\n"; + file_put_contents($absConfigFile, $defaultProjectConfig); + } + + /** + * 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) { + touch(Path::join($absGraphQLDir, "$file.yml")); + } + + // config.yml + $configPath = Path::join($absGraphQLDir, 'config.yml'); + $defaultConfig = <<appNamespace\\$this->graphqlCodeDir\\Resolvers + YAML; + file_put_contents($configPath, $defaultConfig); + + // models.yml + $configPath = Path::join($absGraphQLDir, 'models.yml'); + $defaultConfig = <<projectDir, $this->srcDir); + $absGraphQLCodeDir = Path::join($absSrcDir, $this->graphqlCodeDir); + $graphqlNamespace = implode('\\', [ + $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'); + $moreInfo = 'https://docs.silverstripe.org/en/developer_guides/graphql/' + . 'working_with_generic_types/resolver_discovery/#the-resolver-discovery-pattern'; + $resolverCode = << or resolve + * will be automatically assigned to their respective fields. + * + * More information: $moreInfo + */ + class Resolvers + { + public static function resolveMyQuery(\$obj, array \$args, array \$context): array + { + // Return the result of query { myQuery { ... } } + return [ + 'lorem' => 'ipsum' + ]; + } + } + + PHP; + file_put_contents($resolverFile, $resolverCode); + } + + /** + * Outputs help text to the console + */ + private function showHelp(): void + { + $segment = static::$segment; + echo <<