src/SDL/Base/Definitions/BaseDirective.php

Summary

Maintainability
A
1 hr
Test Coverage
<?php
/**
 * This file is part of Railt package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
declare(strict_types=1);

namespace Railt\SDL\Base\Definitions;

use Railt\SDL\Base\Dependent\Argument\BaseArgumentsContainer;
use Railt\SDL\Contracts\Definitions;
use Railt\SDL\Contracts\Definitions\Definition;
use Railt\SDL\Contracts\Definitions\Directive\Location;
use Railt\SDL\Contracts\Definitions\DirectiveDefinition;
use Railt\SDL\Contracts\Dependent;
use Railt\SDL\Contracts\Document;
use Railt\SDL\Contracts\Type;

/**
 * Class BaseDirectiveDefinition
 */
abstract class BaseDirective extends BaseTypeDefinition implements DirectiveDefinition
{
    use BaseArgumentsContainer;

    /**
     * Type name
     */
    protected const TYPE_NAME = Type::DIRECTIVE;

    /**
     * Directive location type name
     */
    protected const LOCATION_TYPE_NAME = 'DirectiveLocation';

    /**
     * Mappings location to allowed type
     */
    protected const LOCATION_TARGET_MAPPINGS = [
        Location::TARGET_SCHEMA       => Definitions\SchemaDefinition::class,
        Location::TARGET_INPUT_OBJECT => Definitions\InputDefinition::class,

        Location::TARGET_OBJECT     => Definitions\ObjectDefinition::class,
        Location::TARGET_INTERFACE  => Definitions\InterfaceDefinition::class,
        Location::TARGET_UNION      => Definitions\UnionDefinition::class,
        Location::TARGET_SCALAR     => Definitions\ScalarDefinition::class,
        Location::TARGET_ENUM       => Definitions\EnumDefinition::class,
        Location::TARGET_ENUM_VALUE => Definitions\Enum\ValueDefinition::class,

        Location::TARGET_FIELD_DEFINITION    => Dependent\FieldDefinition::class,
        Location::TARGET_ARGUMENT_DEFINITION => Dependent\ArgumentDefinition::class,

        Location::TARGET_INPUT_FIELD_DEFINITION => Dependent\ArgumentDefinition::class,

        // Custom
        Location::TARGET_DOCUMENT => Document::class,
    ];

    /**
     * @var array|string[]
     */
    protected $locations = [];

    /**
     * @var array|null
     */
    private $allLocations;

    /**
     * @return iterable|string[]
     */
    public function getLocations(): iterable
    {
        return \array_values($this->locations);
    }

    /**
     * @return int
     */
    public function getNumberOfLocations(): int
    {
        return \count($this->locations);
    }

    /**
     * @param null|Definition $type
     * @return bool
     */
    public function isAllowedFor(?Definition $type): bool
    {
        if ($type === null) {
            return false;
        }

        foreach (self::LOCATION_TARGET_MAPPINGS as $out => $allowedType) {
            if ($type instanceof $allowedType && $this->hasLocation($out)) {
                //
                // If type is ArgumentType but:
                //
                // 1) Position: InputType > ArgumentType
                //      + Directive Location: ARGUMENT_DEFINITION
                //
                // 2) Position: FieldType > ArgumentType
                //      + Directive Location: INPUT_FIELD_DEFINITION
                //
                if ($type instanceof Dependent\ArgumentDefinition && ! $this->allowedForArgument($type)) {
                    continue;
                }

                return true;
            }
        }

        return false;
    }

    /**
     * @param string $name
     * @return bool
     */
    public function hasLocation(string $name): bool
    {
        $name = \strtoupper($name);

        return \in_array($name, $this->locations, true);
    }

    /**
     * @param Dependent\ArgumentDefinition $type
     * @return bool
     */
    private function allowedForArgument(Dependent\ArgumentDefinition $type): bool
    {
        $location = $type->getParent() instanceof Definitions\InputDefinition
            ? Location::TARGET_INPUT_FIELD_DEFINITION
            : Location::TARGET_ARGUMENT_DEFINITION;

        return $this->hasLocation($location);
    }

    /**
     * @return bool
     */
    public function isAllowedForQueries(): bool
    {
        foreach ($this->locations as $location) {
            if (\in_array($location, Location::TARGET_GRAPHQL_QUERY, true)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @return bool
     */
    public function isAllowedForSchemaDefinitions(): bool
    {
        foreach ($this->locations as $location) {
            if (\in_array($location, Location::TARGET_GRAPHQL_SDL, true)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @return array
     */
    public function __sleep(): array
    {
        return \array_merge(parent::__sleep(), [
            'locations',
            'arguments',
        ]);
    }

    /**
     * @return array
     */
    protected function getAllAllowedLocations(): array
    {
        if ($this->allLocations === null) {
            $locations = new \ReflectionClass(Location::class);

            $this->allLocations = \array_filter(\array_values($locations->getConstants()), '\\is_string');
        }

        return $this->allLocations;
    }
}