Source of file SchemaScaffolder.php
Size: 10,904 Bytes - Last Modified: 2021-12-23T10:31:47+00:00
/var/www/docs.ssmods.com/process/src/src/Scaffolding/Scaffolders/SchemaScaffolder.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388 | <?php namespace SilverStripe\GraphQL\Scaffolding\Scaffolders; use Exception; use InvalidArgumentException; use SilverStripe\Core\Config\Config; use SilverStripe\Core\Extensible; use SilverStripe\Core\Injector\Injector; use SilverStripe\GraphQL\Manager; use SilverStripe\GraphQL\OperationResolver; use SilverStripe\GraphQL\Scaffolding\Extensions\TypeCreatorExtension; use SilverStripe\GraphQL\Scaffolding\Interfaces\ManagerMutatorInterface; use SilverStripe\GraphQL\Scaffolding\StaticSchema; use SilverStripe\GraphQL\Scaffolding\Util\OperationList; use SilverStripe\ORM\ArrayLib; use SilverStripe\View\ViewableData; /** * The entry point for a GraphQL scaffolding definition. Holds DataObject type definitions, * and their nested Mutation/Query definitions. */ class SchemaScaffolder implements ManagerMutatorInterface { use Extensible; const ALL = '*'; const CREATE = 'create'; const READ = 'read'; const UPDATE = 'update'; const DELETE = 'delete'; const READ_ONE = 'readOne'; /** * @var DataObjectScaffolder[] */ protected $types = []; /** * @var OperationList */ protected $queries; /** * @var OperationList */ protected $mutations; /** * Create from an array, e.g. derived from YAML. * * @param array $config * @return self * @throws InvalidArgumentException */ public static function createFromConfig($config) { $scaffolder = Injector::inst()->get(self::class); if (isset($config['types'])) { if (!ArrayLib::is_associative($config['types'])) { throw new InvalidArgumentException( '"types" must be a map of class name to settings.' ); } foreach ($config['types'] as $dataObjectClass => $settings) { $scaffolder->type($dataObjectClass) ->applyConfig($settings); } } $queryMap = [ 'queries' => 'query', 'mutations' => 'mutation', ]; foreach ($queryMap as $group => $method) { if (isset($config[$group])) { if (!ArrayLib::is_associative($config[$group])) { throw new InvalidArgumentException( sprintf( '"%s" must be a map of operation name to settings.', $group ) ); } foreach ($config[$group] as $fieldName => $fieldSettings) { if (!isset($fieldSettings['type'])) { throw new InvalidArgumentException( sprintf( '"%s" must have a "type" field. See %s', $group, $fieldName ) ); } $scaffolder->$method($fieldName, $fieldSettings['type']) ->applyConfig($fieldSettings); } } } return $scaffolder; } /** * Constructor. */ public function __construct() { $this->queries = OperationList::create([]); $this->mutations = OperationList::create([]); } /** * Finds or makes a DataObject definition. * * @param string $class * @return DataObjectScaffolder * @throws InvalidArgumentException */ public function type($class) { // Remove leading backslash. All namespaces are assumed absolute in YAML $class = ltrim($class, '\\'); foreach ($this->types as $scaffold) { if ($scaffold->getDataObjectClass() === $class) { return $scaffold; } } $scaffold = Injector::inst()->create(DataObjectScaffolder::class, $class) ->setChainableParent($this); $this->types[] = $scaffold; return $scaffold; } /** * Find or make a query. * * @param string $name * @param string $class * @param callable|OperationResolver $resolver * @return QueryScaffolder|ListQueryScaffolder */ public function query($name, $class, $resolver = null) { /** * @var QueryScaffolder $query */ $query = $this->queries->findByName($name); if ($query) { return $query; } $operationScaffold = (new ListQueryScaffolder($name, null, $resolver, $class)) ->setChainableParent($this); $this->queries->push($operationScaffold); return $operationScaffold; } /** * Find or make a mutation. * * @param string $name * @param string $class * @param callable|OperationResolver $resolver * @return bool|MutationScaffolder */ public function mutation($name, $class, $resolver = null) { $mutation = $this->mutations->findByName($name); if ($mutation) { return $mutation; } $operationScaffold = (new MutationScaffolder($name, null, $resolver, $class)) ->setChainableParent($this); $this->mutations->push($operationScaffold); return $operationScaffold; } /** * Removes a mutation. * * @param string $name * @return $this */ public function removeMutation($name) { $this->mutations->removeByName($name); return $this; } /** * Removes a query. * * @param string $name * * @return $this */ public function removeQuery($name) { $this->queries->removeByName($name); return $this; } /** * @return DataObjectScaffolder[] */ public function getTypes() { return $this->types; } /** * Returns true if the type has been added to the scaffolder * * @param string $dataObjectClass * @return bool */ public function hasType($dataObjectClass) { foreach ($this->types as $scaffold) { if ($scaffold->getDataObjectClass() == $dataObjectClass) { return true; } } return false; } /** * @return OperationList */ public function getQueries() { return $this->queries; } /** * Gets all nested queries for all types * @return array */ public function getNestedQueries() { $queries = []; foreach ($this->types as $scaffold) { $queries = array_merge($queries, $scaffold->getNestedQueries()); } return $queries; } /** * @return OperationList */ public function getMutations() { return $this->mutations; } /** * Adds every DataObject and its dependencies to the Manager. * * @param Manager $manager */ public function addToManager(Manager $manager) { $this->registerFixedTypes($manager); $this->registerPeripheralTypes($manager); $this->extend('onBeforeAddToManager', $manager); // Add all DataObjects to the manager foreach ($this->types as $scaffold) { $scaffold->addToManager($manager); $inheritanceScaffolder = new InheritanceScaffolder( $scaffold->getDataObjectClass(), StaticSchema::config()->get('inheritanceTypeSuffix') ); // Due to shared ancestry, it's inevitable that the same union type will get added multiple times. if (!$manager->hasType($inheritanceScaffolder->getName())) { $inheritanceScaffolder->addToManager($manager); } } foreach ($this->queries as $scaffold) { $scaffold->addToManager($manager); } foreach ($this->mutations as $scaffold) { $scaffold->addToManager($manager); } $this->extend('onAfterAddToManager', $manager); } /** * Registers special SS types that are made available to all schemas, e.g. DBFile ObjectType * * @param Manager $manager * @throws Exception */ protected function registerFixedTypes(Manager $manager) { $fixedTypes = Config::inst()->get(self::class, 'fixed_types'); if ($fixedTypes) { if (!is_array($fixedTypes)) { throw new Exception( sprintf( '%s.fixed_types must be an array', __CLASS__ ) ); } foreach ($fixedTypes as $className) { $instance = Injector::inst()->get($className); if (!$instance instanceof ViewableData) { throw new Exception( sprintf( 'Cannot auto register class %s. It is not a subclass of %s', $className, ViewableData::class ) ); } if (!$instance->hasExtension(TypeCreatorExtension::class)) { throw new Exception( sprintf( 'Cannot auto register class %s. Is does not have the extension %s.', $className, TypeCreatorExtension::class ) ); } $instance->addToManager($manager); } } } /** * Registers types and respective operations for all ancestors of exposed dataobjects * @param Manager $manager */ protected function registerPeripheralTypes(Manager $manager) { $schema = StaticSchema::inst(); foreach ($this->types as $scaffold) { // Add dependent classes, e.g has_one, has_many nested queries foreach ($scaffold->getDependentClasses() as $class) { $this->type($class); // Implicitly, all subclasses are added (albeit with no fields) foreach ($schema->getDescendants($class) as $subclass) { $this->type($subclass); } } $tree = array_merge( $schema->getAncestry($scaffold->getDataObjectClass()), $schema->getDescendants($scaffold->getDataObjectClass()) ); // Expose all the classes along the inheritance chain foreach ($tree as $class) { $newType = $this->type($class); $scaffold->cloneTo($newType); } } } } |