gdbots/pbjc-php

View on GitHub
src/FieldDescriptor.php

Summary

Maintainability
C
1 day
Test Coverage
<?php

namespace Gdbots\Pbjc;

use Gdbots\Common\Util\NumberUtils;
use Gdbots\Common\Util\StringUtils;
use Gdbots\Pbjc\Enum\FieldRule;
use Gdbots\Pbjc\Enum\Format;
use Gdbots\Pbjc\Type\AbstractStringType;
use Gdbots\Pbjc\Type\IntEnumType;
use Gdbots\Pbjc\Type\Type;
use Gdbots\Pbjc\Util\LanguageBag;

final class FieldDescriptor
{
    /**
     * Regular expression pattern for matching a valid field name.  The pattern allows
     * for camelCase fields name but snake_case is recommend.
     *
     * @constant string
     */
    const VALID_NAME_PATTERN = '/^([a-zA-Z_]{1}[a-zA-Z0-9_]+)$/';

    /** @var string */
    private $name;

    /** @var string */
    private $description;

    /** @var Type */
    private $type;

    /** @var FieldRule */
    private $rule;

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

    /**
     * A regular expression to match against for string types.
     *
     * @link http://spacetelescope.github.io/understanding-json-schema/reference/string.html#pattern
     *
     * @var string
     */
    private $pattern;

    /**
     * @link http://spacetelescope.github.io/understanding-json-schema/reference/string.html#format
     *
     * @var Format
     */
    private $format;

    /** @var int */
    private $minLength;

    /** @var int */
    private $maxLength;

    /** @var int */
    private $min;

    /** @var int */
    private $max;

    /** @var int */
    private $precision;

    /** @var int */
    private $scale;

    /** @var mixed */
    private $default;

    /** @var bool */
    private $useTypeDefault = true;

    /** @var SchemaDescriptor[] */
    private $anyOf = [];

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

    /** @var EnumDescriptor */
    private $enum;

    /** @var LanguageBag */
    private $languages;

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

    /**
     * @param string $name
     * @param array  $parameters
     *
     * @throws \InvalidArgumentException
     */
    public function __construct($name, array $parameters)
    {
        if (!$name || strlen($name) > 127 || preg_match(self::VALID_NAME_PATTERN, $name) === false) {
            throw new \InvalidArgumentException(
                sprintf(
                    'Field [%s] must match pattern [%s] with less than 127 characters.',
                    $name,
                    self::VALID_NAME_PATTERN
                )
            );
        }

        foreach ($parameters as $key => $value) {
            $classProperty = lcfirst(StringUtils::toCamelFromSlug($key));

            // existing properties
            if (property_exists(get_called_class(), $classProperty)) {
                switch ($classProperty) {
                    case 'name':
                    case 'languages':
                        continue 2;

                    case 'type':
                        /** @var \Gdbots\Pbjc\Type\Type $class */
                        $class = sprintf(
                            '\\Gdbots\\Pbjc\\Type\\%sType',
                            StringUtils::toCamelFromSlug($parameters['type'])
                        );

                        $value = $class::create();
                        break;

                    case 'rule':
                        if (null !== $value && in_array($value, FieldRule::values())) {
                            $value = FieldRule::create($value);
                        }
                        break;

                    case 'format':
                        if (null !== $value && in_array($value, Format::values())) {
                            $value = Format::create($value);
                        }
                        break;

                    case 'required':
                    case 'useTypeDefault':
                    case 'overridable':
                    case 'deprecated':
                        $value = (bool)$value;
                        break;

                    case 'min':
                    case 'max':
                    case 'minLength':
                    case 'maxLength':
                    case 'precision':
                    case 'scale':
                        $value = (int)$value;
                        break;
                }

                $this->$classProperty = $value;
            } // language options
            elseif (substr($key, -8) == '-options') {
                $language = substr($key, 0, -8); // remove "-options"

                if (is_array($value)) {
                    $value = new LanguageBag($value);
                }

                $this->getLanguages()->set($language, $value);
            }
        }

        $this->name = $name;

        $this->applyDefaults();
        $this->applyFieldRule();
        $this->applyStringOptions();
        $this->applyNumericOptions();
    }

    private function applyDefaults()
    {
        $this->format = $this->format ?: Format::UNKNOWN();
    }

    /**
     * @throws \InvalidArgumentException
     */
    private function applyFieldRule()
    {
        $this->rule = $this->rule ?: FieldRule::A_SINGLE_VALUE();
        if ($this->isASet() && !$this->type->allowedInSet()) {
            throw new \InvalidArgumentException(
                sprintf(
                    'Field [%s] with type [%s] cannot be used in a set.',
                    $this->name,
                    $this->type->getTypeValue()
                )
            );
        }
    }

    private function applyStringOptions()
    {
        // use *Length for string type
        if ($this->type instanceof AbstractStringType) {
            $this->minLength = $this->min;
            $this->maxLength = $this->max;
            $this->min = null;
            $this->max = null;
        }

        if ($this->maxLength > 0) {
            $this->minLength = NumberUtils::bound($this->minLength, 0, $this->maxLength);
        } else {
            // arbitrary string minimum range
            $this->minLength = NumberUtils::bound($this->minLength, 0, $this->type->getMaxBytes());
        }
    }

    private function applyNumericOptions()
    {
        if (null !== $this->min) {
            if (null !== $this->max) {
                if ($this->min > $this->max) {
                    $this->min = $this->max;
                }
            }
        }

        $this->precision = NumberUtils::bound($this->precision, 0, 65); // range 1-65 (we use 0 to ignore when generating class)
        $this->scale = NumberUtils::bound($this->scale, 0, $this->precision);
    }

    /**
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @return string
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * @return \Gdbots\Pbjc\Type\Type
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * @return FieldRule
     */
    public function getRule()
    {
        return $this->rule;
    }

    /**
     * @return bool
     */
    public function isASingleValue()
    {
        //return FieldRule::A_SINGLE_VALUE === $this->rule->getValue();
        return false;
    }

    /**
     * @return bool
     */
    public function isASet()
    {
        return FieldRule::A_SET === $this->rule->getValue();
    }

    /**
     * @return bool
     */
    public function isAList()
    {
        return FieldRule::A_LIST === $this->rule->getValue();
    }

    /**
     * @return bool
     */
    public function isAMap()
    {
        return FieldRule::A_MAP === $this->rule->getValue();
    }

    /**
     * @return bool
     */
    public function isRequired()
    {
        return $this->required;
    }

    /**
     * @return string
     */
    public function getPattern()
    {
        return $this->pattern;
    }

    /**
     * @return Format|null
     */
    public function getFormat()
    {
        if ($this->format === Format::UNKNOWN()) {
            return;
        }

        return $this->format;
    }

    /**
     * @return int
     */
    public function getMinLength()
    {
        return $this->minLength;
    }

    /**
     * @return int
     */
    public function getMaxLength()
    {
        return $this->maxLength;
    }

    /**
     * @return int
     */
    public function getMin()
    {
        return $this->min;
    }

    /**
     * @return int
     */
    public function getMax()
    {
        return $this->max;
    }

    /**
     * @return int
     */
    public function getPrecision()
    {
        return $this->precision;
    }

    /**
     * @return int
     */
    public function getScale()
    {
        return $this->scale;
    }

    /**
     * @return mixed
     */
    public function getDefault()
    {
        if (null === $this->default) {
            if ($this->useTypeDefault) {
                return $this->isASingleValue() ? $this->type->getDefault() : [];
            }

            return $this->isASingleValue() ? null : [];
        }

        if ($this->type instanceof IntEnumType) {
            return (int)$this->default;
        }

        return $this->default;
    }

    /**
     * @return bool
     */
    public function isUseTypeDefault()
    {
        return $this->useTypeDefault;
    }

    /**
     * @return SchemaDescriptor[]
     */
    public function getAnyOf()
    {
        return $this->anyOf;
    }

    /**
     * @return bool
     */
    public function isOverridable()
    {
        return $this->overridable;
    }

    /**
     * @return EnumDescriptor
     */
    public function getEnum()
    {
        return $this->enum;
    }

    /**
     * @return LanguageBag
     */
    public function getLanguages()
    {
        return $this->languages ?: $this->languages = new LanguageBag();
    }

    /**
     * @param string $language
     *
     * @return LanguageBag
     */
    public function getLanguage($language)
    {
        if (!$this->getLanguages()->has($language)) {
            $this->getLanguages()->set($language, new LanguageBag());
        }

        return $this->getLanguages()->get($language);
    }

    /**
     * @return bool
     */
    public function isDeprecated()
    {
        return $this->deprecated;
    }
}