Skip to content
This repository has been archived by the owner on Mar 8, 2023. It is now read-only.

Commit

Permalink
Fix lazy-loading of properties
Browse files Browse the repository at this point in the history
  • Loading branch information
crisu83 committed Feb 10, 2018
1 parent 31c6533 commit eab2b14
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 82 deletions.
43 changes: 24 additions & 19 deletions src/Type/Definition/Behavior/FieldsTrait.php
Expand Up @@ -18,7 +18,12 @@ trait FieldsTrait
/**
* @var Field[]
*/
private $_fieldMap;
private $_fieldMap = [];

/**
* @var bool
*/
private $_isFieldMapBuilt = false;

/**
* @param string $name
Expand All @@ -27,31 +32,18 @@ trait FieldsTrait
*/
public function getField(string $name): ?Field
{
$this->buildFieldMap();
$this->buildFieldMapIfNecessary();

return $this->_fieldMap[$name] ?? null;
}

/**
* @return Field[]
* @throws \Exception
*/
public function getFields(): array
{
$this->buildFieldMap();

return $this->_fieldMap ?? [];
}

/**
* @param Field $field
* @return $this
* @throws \Exception
*/
public function addField(Field $field)
{
$this->buildFieldMap();

$this->_fieldMap[$field->getName()] = $field;

return $this;
Expand All @@ -71,13 +63,26 @@ public function addFields(array $fields)
}

/**
* @return Field[]
* @throws \Exception
*/
protected function buildFieldMap()
public function getFields(): array
{
// Fields are lazy-loaded to avoid concurrency issues.
if ($this->_fieldMap === null) {
$this->_fieldMap = $this->defineFieldMap($this->_fieldsThunk);
$this->buildFieldMapIfNecessary();

return $this->_fieldMap;
}

/**
* @throws \Exception
*/
protected function buildFieldMapIfNecessary(): void
{
// Fields are built lazily to avoid concurrency issues.
if (!$this->_isFieldMapBuilt) {
$this->_fieldMap = array_merge($this->defineFieldMap($this->_fieldsThunk), $this->_fieldMap);

$this->_isFieldMapBuilt = true;
}
}

Expand Down
20 changes: 13 additions & 7 deletions src/Type/Definition/ObjectType.php
Expand Up @@ -79,7 +79,12 @@ class ObjectType implements TypeInterface, CompositeTypeInterface, NamedTypeInte
/**
* @var InterfaceType[]
*/
private $_interfaces;
private $_interfaces = [];

/**
* @var bool
*/
private $_isInterfacesBuilt = false;

/**
* @inheritdoc
Expand Down Expand Up @@ -117,7 +122,7 @@ public function isTypeOf($value, $context, $info): bool
*/
public function getInterfaces(): array
{
$this->buildInterfaces();
$this->buildInterfacesIfNecessary();

return $this->_interfaces;
}
Expand All @@ -137,8 +142,6 @@ public function getIsTypeOf(): ?callable
*/
protected function addInterface(InterfaceType $interface)
{
$this->buildInterfaces();

$this->_interfaces[] = $interface;

return $this;
Expand Down Expand Up @@ -183,10 +186,13 @@ protected function setIsTypeOf(?callable $isTypeOf)
/**
* @throws \Exception
*/
protected function buildInterfaces()
protected function buildInterfacesIfNecessary()
{
if ($this->_interfaces === null) {
$this->_interfaces = $this->defineInterfaces($this->_interfacesThunk);
// Interfaces are built lazily to avoid concurrency issues.
if (!$this->_isInterfacesBuilt) {
$this->_interfaces = array_merge($this->defineInterfaces($this->_interfacesThunk), $this->_interfaces);

$this->_isInterfacesBuilt = true;
}
}

Expand Down
34 changes: 26 additions & 8 deletions src/Type/Definition/UnionType.php
Expand Up @@ -58,7 +58,12 @@ class UnionType implements AbstractTypeInterface, CompositeTypeInterface, Output
/**
* @var TypeInterface[]
*/
private $_typeMap;
private $_typeMap = [];

/**
* @var bool
*/
private $_isTypeMapBuilt = false;

/**
* @inheritdoc
Expand All @@ -68,22 +73,33 @@ protected function beforeConfig(): void
$this->setName(TypeEnum::UNION);
}

/**
* @param TypeInterface $type
* @return UnionType
*/
public function addType(TypeInterface $type): UnionType
{
$this->_typeMap[$type->getName()] = $type;

return $this;
}

/**
* @return TypeInterface[]
* @throws \Exception
*/
public function getTypes(): array
{
$this->buildTypeMap();
$this->buildTypeMapIfNecessary();

return $this->_typeMap;
}

/**
* @param array|callable $typesThunk
* @return $this
* @return UnionType
*/
protected function setTypes($typesThunk)
protected function setTypes($typesThunk): UnionType
{
$this->_typesThunk = $typesThunk;

Expand All @@ -93,11 +109,13 @@ protected function setTypes($typesThunk)
/**
* @throws \Exception
*/
protected function buildTypeMap()
protected function buildTypeMapIfNecessary()
{
// Types are lazy-loaded to avoid concurrency issues.
if ($this->_typeMap === null) {
$this->_typeMap = $this->defineTypes($this->_typesThunk);
// Types are built lazily to avoid concurrency issues.
if (!$this->_isTypeMapBuilt) {
$this->_typeMap = array_merge($this->defineTypes($this->_typesThunk), $this->_typeMap);

$this->_isTypeMapBuilt = true;
}
}

Expand Down
104 changes: 60 additions & 44 deletions src/Type/Schema/Schema.php
Expand Up @@ -152,53 +152,12 @@ protected function beforeConfig(): void
}

/**
* @return Schema
* @throws \Exception
*/
public function build(): Schema
protected function afterConfig(): void
{
$initialTypes = [
$this->query,
$this->mutation,
$this->subscription,
];

if ($this->types) {
$initialTypes = array_merge($initialTypes, $this->types);
}

// Keep track of all types referenced within the schema.
$typeMap = [];

// First by deeply visiting all initial types.
$typeMap = array_reduce($initialTypes, function ($map, $type) {
return typeMapReducer($map, $type);
}, $typeMap);

// Then by deeply visiting all directive types.
$typeMap = array_reduce($this->directives, function ($map, $directive) {
return typeMapDirectiveReducer($map, $directive);
}, $typeMap);

// Storing the resulting map for reference by the schema.
$this->_typeMap = $typeMap;

// Keep track of all implementations by interface name.
foreach ($this->_typeMap as $typeName => $type) {
if ($type instanceof ObjectType) {
foreach ($type->getInterfaces() as $interface) {
$interfaceName = $interface->getName();

if (!isset($this->_implementations[$interfaceName])) {
$this->_implementations[$interfaceName] = [];
}

$this->_implementations[$interfaceName][] = $type;
}
}
}

return $this;
$this->buildTypeMap();
$this->buildImplementations();
}

/**
Expand Down Expand Up @@ -248,6 +207,63 @@ protected function getPossibleTypes(AbstractTypeInterface $abstractType): ?array
return $this->_implementations[$abstractType->getName()] ?? null;
}

/**
*
*/
protected function buildTypeMap(): void
{
$initialTypes = [
$this->query,
$this->mutation,
$this->subscription,
];

if ($this->types) {
$initialTypes = array_merge($initialTypes, $this->types);
}

// Keep track of all types referenced within the schema.
$typeMap = [];

// First by deeply visiting all initial types.
$typeMap = array_reduce($initialTypes, function ($map, $type) {
return typeMapReducer($map, $type);
}, $typeMap);

// Then by deeply visiting all directive types.
$typeMap = array_reduce($this->directives, function ($map, $directive) {
return typeMapDirectiveReducer($map, $directive);
}, $typeMap);

// Storing the resulting map for reference by the schema.
$this->_typeMap = $typeMap;
}

/**
* @throws \Exception
*/
protected function buildImplementations()
{
$implementations = [];

// Keep track of all implementations by interface name.
foreach ($this->_typeMap as $typeName => $type) {
if ($type instanceof ObjectType) {
foreach ($type->getInterfaces() as $interface) {
$interfaceName = $interface->getName();

if (!isset($implementations[$interfaceName])) {
$implementations[$interfaceName] = [];
}

$implementations[$interfaceName][] = $type;
}
}
}

$this->_implementations = $implementations;
}

/**
* @param ObjectType $query
* @return Schema
Expand Down
8 changes: 4 additions & 4 deletions tests/Functional/Type/DefinitionTest.php
Expand Up @@ -185,7 +185,7 @@ protected function schemaWithField(TypeInterface $type): Schema
],
]),
'types' => [$type],
])->build();
]);
}

/**
Expand All @@ -212,7 +212,7 @@ protected function schemaWithObjectWithFieldResolver($resolveValue): Schema
'f' => ['type' => $badResolverType],
],
])
])->build();
]);
}

// Type System: Example
Expand Down Expand Up @@ -381,7 +381,7 @@ public function testIncludesNestedInputObjectsInSchemaTypeMap()
'subscription' => $someSubscription,
]);

$this->assertEquals($nestedInputObject, $schema->build()->getTypeMap()[$nestedInputObject->getName()]);
$this->assertEquals($nestedInputObject, $schema->getTypeMap()[$nestedInputObject->getName()]);
}

/**
Expand Down Expand Up @@ -414,7 +414,7 @@ public function testIncludesInterfacePossibleTypesInSchemaTypeMap()
'types' => [$someSubtype],
]);

$this->assertEquals($someSubtype, $schema->build()->getTypeMap()[$someSubtype->getName()]);
$this->assertEquals($someSubtype, $schema->getTypeMap()[$someSubtype->getName()]);
}

/**
Expand Down

0 comments on commit eab2b14

Please sign in to comment.