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

NEW: Bulk loader #432

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 0 additions & 1 deletion _config/config.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
---
Name: graphqlconfig
---
# Define the type parsers
SilverStripe\Core\Injector\Injector:
SilverStripe\GraphQL\QueryHandler\QueryHandlerInterface:
class: SilverStripe\GraphQL\QueryHandler\QueryHandler
Expand Down
4 changes: 4 additions & 0 deletions _config/schema-global.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ SilverStripe\GraphQL\Schema\Schema:
defaultResolver: 'SilverStripe\GraphQL\Schema\Resolver\DefaultResolver::defaultFieldResolver'
modelCreators:
- 'SilverStripe\GraphQL\Schema\DataObject\ModelCreator'
defaultBulkLoad:
inheritanceLoader:
include:
- SilverStripe\ORM\DataObject
modelConfig:
DataObject:
type_formatter: 'SilverStripe\Core\ClassInfo::shortName'
Expand Down
97 changes: 97 additions & 0 deletions src/Schema/BulkLoader/AbstractBulkLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

namespace SilverStripe\GraphQL\Schema\BulkLoader;

use SilverStripe\Core\Injector\Injectable;
use SilverStripe\GraphQL\Schema\Exception\SchemaBuilderException;
use SilverStripe\GraphQL\Schema\Interfaces\ConfigurationApplier;
use SilverStripe\GraphQL\Schema\Interfaces\Identifiable;
use SilverStripe\GraphQL\Schema\Schema;

/**
* Provides base functionality to all bulk loaders. Should override the collect()
* method with computations that parse the include/exclude directives and return
* a collection of classes.
*/
abstract class AbstractBulkLoader implements Identifiable, ConfigurationApplier
{
use Injectable;

/**
* @var string[]
*/
protected $includeList;

/**
* @var string[]
*/
protected $excludeList;

/**
* AbstractBulkLoader constructor.
* @param array $include
* @param array $exclude
*/
public function __construct(array $include = [], array $exclude = [])
{
$this->includeList = $include;
$this->excludeList = $exclude;
}

/**
* @param array $include
* @return $this
*/
public function include(array $include): self
{
$this->includeList = $include;

return $this;
}

/**
* @param array $exclude
* @return $this
*/
public function exclude(array $exclude): self
{
$this->excludeList = $exclude;

return $this;
}

/**
* @param array $config
* @return AbstractBulkLoader
* @throws SchemaBuilderException
*/
public function applyConfig(array $config): self
{
Schema::assertValidConfig($config, ['include', 'exclude'], ['include']);
$include = $config['include'];
$exclude = $config['exclude'] ?? [];
if (!is_array($include)) {
$include = [$include];
}
if (!is_array($exclude)) {
$exclude = [$exclude];
}
return $this
->include($include)
->exclude($exclude);
}

/**
* @param Collection $collection
* @return Collection
*/
public function collect(Collection $collection): Collection
{
return Collection::create($collection->getManifest());
}

/**
* @return string
*/
abstract public static function getIdentifier(): string;
}
128 changes: 128 additions & 0 deletions src/Schema/BulkLoader/BulkLoaderSet.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?php

namespace SilverStripe\GraphQL\Schema\BulkLoader;

use InvalidArgumentException;
use SilverStripe\Core\ClassInfo;
use ReflectionException;
use SilverStripe\Core\Injector\Injectable;
use SilverStripe\GraphQL\Schema\Exception\SchemaBuilderException;
use SilverStripe\GraphQL\Schema\Interfaces\ConfigurationApplier;
use SilverStripe\GraphQL\Schema\Logger;
use SilverStripe\GraphQL\Schema\Schema;

/**
* Composed with a list of bulk loaders to be executed in serial and return the aggregate result
* of all their collect() calls
*/
class BulkLoaderSet implements ConfigurationApplier
{
use Injectable;

/**
* @var AbstractBulkLoader[]
*/
private $loaders;

/**
* @var Collection
*/
private $initialCollection;

/**
* BulkLoaderSet constructor.
* @param AbstractBulkLoader[] $loaders
* @param Collection|null $initialCollection
* @throws ReflectionException
*/
public function __construct(array $loaders = [], ?Collection $initialCollection = null)
{
$this->setLoaders($loaders);
if ($initialCollection) {
$this->initialCollection = $initialCollection;
} else {
$this->initialCollection = Collection::createFromClassList(ClassInfo::allClasses());
}
}

/**
* @param array $config
* @return $this
* @throws SchemaBuilderException
*/
public function applyConfig(array $config): self
{
$registry = Registry::inst();
foreach ($config as $loaderID => $loaderConfig) {
/* @var AbstractBulkLoader $loader */
$loader = $registry->getByID($loaderID);
Schema::invariant($loader, 'Loader "%s" does not exist', $loaderID);
$loader->applyConfig($loaderConfig);
$this->addLoader($loader);
}

return $this;
}

/**
* @param AbstractBulkLoader $loader
* @return $this
*/
public function addLoader(AbstractBulkLoader $loader): self
{
$this->loaders[] = $loader;

return $this;
}

/**
* @return Collection
*/
public function process(): Collection
{
$logger = Logger::singleton();
$collection = $this->initialCollection;
$logger->debug(sprintf(
'Bulk loader initial collection size: %s',
count($collection->getClasses())
));
foreach ($this->loaders as $loader) {
$collection = $loader->collect($collection);
$logger->debug(sprintf(
'Loader %s reduced bulk load to %s',
$loader->getIdentifier(),
count($collection->getClasses())
));
}

return $collection;
}

/**
* @param array $loaders
* @return $this
*/
public function setLoaders(array $loaders): self
{
foreach ($loaders as $loader) {
if (!$loader instanceof AbstractBulkLoader) {
throw new InvalidArgumentException(sprintf(
'%s only accepts instances of %s',
static::class,
AbstractBulkLoader::class
));
}
}
$this->loaders = $loaders;

return $this;
}

/**
* @return AbstractBulkLoader[]
*/
public function getLoaders(): array
{
return $this->loaders;
}
}
115 changes: 115 additions & 0 deletions src/Schema/BulkLoader/Collection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?php

namespace SilverStripe\GraphQL\Schema\BulkLoader;

use SilverStripe\Core\Injector\Injectable;
use Exception;
use ReflectionClass;
use ReflectionException;

/**
* Defines a collection of class names paired with file paths
*/
class Collection
{
use Injectable;

/**
* @var array
*/
private $manifest;

/**
* Collection constructor.
* @param array $manifest An array of classname keys to filepath values ['My\Class' => '/path/to/Class.php']
* @throws Exception
*/
public function __construct(array $manifest = [])
{
$this->setManifest($manifest);
}

/**
* @param array $manifest
* @return $this
* @throws Exception
*/
public function setManifest(array $manifest): self
{
$this->manifest = $manifest;

return $this;
}

/**
* @param string $class
* @return $this
*/
public function removeClass(string $class): self
{
unset($this->manifest[$class]);

return $this;
}

/**
* @param string $path
* @return $this
*/
public function removeFile(string $path): self
{
$class = array_search($path, $this->manifest);
unset($this->manifest[$class]);

return $this;
}

/**
* @return array
*/
public function getClasses(): array
{
return array_keys($this->manifest);
}

/**
* @return array
*/
public function getFiles(): array
{
return array_values($this->manifest);
}

/**
* @return array
*/
public function getManifest(): array
{
return $this->manifest;
}

/**
* @param array $classList
* @return Collection
* @throws ReflectionException
* @throws Exception
*/
public static function createFromClassList(array $classList): Collection
{
$manifest = [];
foreach ($classList as $class) {
if (!class_exists($class)) {
continue;
}
$reflection = new ReflectionClass($class);
$filePath = $reflection->getFileName();
if (!$filePath) {
continue;
}

$manifest[$class] = $filePath;
}

return new static($manifest);
}
}
Loading