Parses all property information for a single class or interface.

You must pass an xml element that refers to either the class or interface element from
structure.xml.

@param SimpleXMLElement $class

@return array
967620
protected function parseProperties(SimpleXMLElement $class)
    {
        $properties = [];

        $className = (string)$class->full_name;
        $className = ltrim($className, '\\');

        foreach ($class->property as $xProperty) {
            $type = 'mixed';
            $propName = (string)$xProperty->name;
            $default = (string)$xProperty->default;

            $xVar = $xProperty->xpath('docblock/tag[@name="var"]');

            if (count($xVar)) {
                $type = $xVar[0]->type;
            }

            $visibility = (string)$xProperty['visibility'];
            $signature = sprintf('%s %s %s', $visibility, $type, $propName);

            if ($default) {
                $signature .= ' = ' . $default;
            }

            $properties[$propName] = [
                'name'        => $propName,
                'type'        => $type,
                'default'     => $default,
                'description' => (string)$xProperty->docblock->description . "\n\n" . (string)$xProperty->docblock->{'long-description'},
                'visibility'  => $visibility,
                'static'      => ((string)$xProperty['static']) == 'true',
                'signature'   => $signature,
                'deprecated'  => count($class->xpath('docblock/tag[@name="deprecated"]')) > 0,
                'definedBy'   => $className,
            ];
        }

        return $properties;
    }
1091463
public function parse(RefClass $class)
    {
        $result = [];

        $annotations = $this->parseComment($class->docComment);
        foreach ($annotations as $annotation) {
            $annotation->originClass = $class->name;
            $result[]                = $annotation;
        }

        foreach ($class->constants as $constant) {
            foreach ($this->parseComment($constant->docComment) as $annotation) {
                $annotation->originClass    = $class->name;
                $annotation->originConstant = $constant->name;
                $result[]                   = $annotation;
            }
        }

        foreach ($class->properties as $property) {
            foreach ($this->parseComment($property->docComment) as $annotation) {
                $annotation->originClass    = $class->name;
                $annotation->originProperty = $property->name;
                $result[]                   = $annotation;
            }
        }

        foreach ($class->methods as $method) {
            foreach ($this->parseComment($method->docComment) as $annotation) {
                $annotation->originClass  = $class->name;
                $annotation->originMethod = $method->name;
                $result[]                 = $annotation;
            }
        }

        return $result;
    }
1095898
protected function readAttributesArray(\SimpleXMLElement $element): array
    {
        $attributes = [];
        foreach ($element->attributes() as $attrName => $attr) {
            $attributes[(string)$attrName] = (string)$attr;
        }

        return $attributes;
    }
1346227
public function getEventSubscriberInformation($class)
    {
        $events = array();
        $reflectionClass = new \ReflectionClass($class);
        $interfaces = $reflectionClass->getInterfaceNames();
        foreach ($interfaces as $interface) {
            if ($interface == 'Symfony\\Component\\EventDispatcher\\EventSubscriberInterface') {
                return $class::getSubscribedEvents();
            }
        }

        return $events;
    }
667026
private function getClassConstructorInfo(ReflectionClass $class)
    {
        if ($constructor = $class->getConstructor()) {
            $args = [];
            foreach ($constructor->getParameters() as $param) {
                $contracts = [];
                $concreteClassName = null;

                $contract = $param->getClass();
                if ($contract instanceof ReflectionClass) {
                    if (!$contract->isInterface()) {
                        $concreteClassName = $contract->getName();
                    }
                    $contracts = array_merge([$contract->getName()], $contract->getInterfaceNames());
                }

                $args[] = [
                    $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null,
                    $contracts,
                    $concreteClassName
                ];
            }

            return [$constructor->name, $args];
        }

        return null;
    }
1000621
private function getName($class)
    {
        $class = preg_replace('/(.*[^\\\]+)Interface$/', '$1', $class);

        if (isset($this->entityOverride[$class])) {
            $class = $this->entityOverride[$class];
        }
        if ($this->sectionConfig->hasSection($class)) {
            return $this->sectionConfig->getSection($class)->getAlias();
        }

        return $this->objectNamer->name($class);
    }
1194778
public function resolveClassNameToType($class): Type
    {
        if ($class instanceof Node\Expression) {
            return new Types\Mixed_;
        }
        if ($class instanceof PhpParser\Token && $class->kind === PhpParser\TokenKind::ClassKeyword) {
            // Anonymous class
            return new Types\Object_;
        }
        if ($class instanceof PhpParser\Token && $class->kind === PhpParser\TokenKind::StaticKeyword) {
            // `new static`
            return new Types\Static_;
        }
        $className = (string)$class->getResolvedName();

        if ($className === 'self' || $className === 'parent') {
            $classNode = $class->getFirstAncestor(Node\Statement\ClassDeclaration::class);
            if ($className === 'parent') {
                if ($classNode === null || $classNode->classBaseClause === null) {
                    return new Types\Object_;
                }
                // parent is resolved to the parent class
                $classFqn = (string)$classNode->classBaseClause->baseClass->getResolvedName();
            } else {
                if ($classNode === null) {
                    return new Types\Self_;
                }
                // self is resolved to the containing class
                $classFqn = (string)$classNode->getNamespacedName();
            }
            return new Types\Object_(new Fqsen('\\' . $classFqn));
        }
        return new Types\Object_(new Fqsen('\\' . $className));
    }
983559
public function parseClass($class)
    {
        $reflectionClass = new ReflectionClass($class);

        // check if class is entity
        if ($this->reader->getClassAnnotation($reflectionClass, '\ProAI\Datamapper\Annotations\Entity')) {
            return $this->parseEntity($class);
        } else {
            return null;
        }
    }
403411
public function getTemplateForClass($class)
    {
        foreach (ClassTools::singleton()->getReverseAncestry($class) as $name) {
            
            if ($type = PageType::findByClass($name)) {
                
                if ($type->hasPageTemplate()) {
                    return $type->getPageTemplate();
                }
                
            }
            
            if ($name == SiteTree::class) {
                break;
            }
            
        }
    }
892573
public function getImplements($class)
    {
        $interfaces = [];
        $classId = $this->getClassId($class);
        if (isset($classId)) {
            if (isset($this->implements[$classId])) {
                foreach ($this->implements[$classId] as $interfaceId) {
                    $interfaces[$this->interfaces[$interfaceId]] = 1;
                }
            }
            foreach ($this->getAncestors($class) as $parent) {
                foreach ($this->getImplements($parent) as $interface) {
                    $interfaces[$interface] = 1;
                }
            }
        }
        return array_keys($interfaces);
    }
735180
private function getInterface($class) {
        $interfaceName = $class;
        $interfaces = class_implements($class);
        if (!empty($interfaces)) {
            if (count($interfaces) == 1) {
                $interfaceName = array_shift($interfaces);
            } elseif ($key = array_search($class . 'Interface', $interfaces)) {
                $interfaceName = $interfaces[$key];
            }
        }

        return $interfaceName;
    }
781640
public function getClassProperties($class)
    {
        $result = '';
        foreach($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property)
        {
            $static = $property->isStatic() ? ' static' : '';
            $name = $property->name;
            $result .= <<<CODE
    public{$static} \${$name};

CODE;
        }
        return $result;
    }
658219
public function parseEntity($class)
    {
        $reflectionClass = new ReflectionClass($class);

        // scan class annotations
        $classAnnotations = $this->reader->getClassAnnotations($reflectionClass);

        // init class metadata
        $entityMetadata = new EntityDefinition([
            'class' => $class,
            'morphClass' => $this->generateMorphClass($class),
            'table' => new TableDefinition([
                'name' => $this->generateTableName($class),
                'columns' => [],
            ]),
            'versionTable' => false,
        
            'softDeletes' => false,
            'timestamps' => false,
            
            'touches' => [],
            'with' => [],

            'columns' => [],
            'attributes' => [],
            'embeddeds' => [],
            'relations' => [],
        ]);

        // find entity parameters and plugins
        foreach ($classAnnotations as $annotation) {
            // entity parameters
            if ($annotation instanceof \ProAI\Datamapper\Annotations\Entity) {
                if (! empty($annotation->morphClass)) {
                    $entityMetadata['morphClass'] = $annotation->morphClass;
                }
                if (! empty($annotation->touches)) {
                    $entityMetadata['touches'] = $annotation->touches;
                }
                if (! empty($annotation->with)) {
                    $entityMetadata['with'] = $annotation->with;
                }
            }

            // softdeletes
            if ($annotation instanceof \ProAI\Datamapper\Annotations\SoftDeletes) {
                $entityMetadata['softDeletes'] = true;
            }

            // table name
            if ($annotation instanceof \ProAI\Datamapper\Annotations\Table) {
                $entityMetadata['table']['name'] = $annotation->name;
            }

            // timestamps
            if ($annotation instanceof \ProAI\Datamapper\Annotations\Timestamps) {
                $entityMetadata['timestamps'] = true;
            }
        }

        // find versionable annotation (2nd loop, because table name is required)
        foreach ($classAnnotations as $annotation) {
            if ($annotation instanceof \ProAI\Datamapper\Annotations\Versionable) {
                $entityMetadata['versionTable'] = new TableDefinition([
                    'name' => $entityMetadata['table']['name'] . '_version',
                    'columns' => [],
                ]);
            }
        }

        // find columns and embedded classes
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
            $name = $this->getSanitizedName($reflectionProperty->getName(), $entityMetadata['class']);
            $propertyAnnotations = $this->reader->getPropertyAnnotations($reflectionProperty);

            foreach ($propertyAnnotations as $annotation) {
                // column
                if ($annotation instanceof \ProAI\Datamapper\Annotations\Column) {
                    $this->setAdditionalColumnProperties($name, $annotation, $propertyAnnotations);

                    $this->parseColumn($name, $annotation, $entityMetadata, false, true);
                }

                // embedded class
                if ($annotation instanceof \ProAI\Datamapper\Annotations\Embedded) {
                    $this->parseEmbeddedClass($name, $annotation, $entityMetadata, true);
                }
            }
        }

        // check primary key
        $this->validator->validatePrimaryKey($entityMetadata);

        // find relationships (2nd loop, because primary key column is required for foreign keys)
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
            $name = $this->getSanitizedName($reflectionProperty->getName(), $entityMetadata['class']);
            $propertyAnnotations = $this->reader->getPropertyAnnotations($reflectionProperty);

            foreach ($propertyAnnotations as $annotation) {
                // column
                if ($annotation instanceof \ProAI\Datamapper\Annotations\Column) {
                    $this->setAdditionalColumnProperties($name, $annotation, $propertyAnnotations);

                    $entityMetadata['attributes'][] = $this->parseColumn($name, $annotation, $entityMetadata);
                }

                // embedded class
                if ($annotation instanceof \ProAI\Datamapper\Annotations\Embedded) {
                    $entityMetadata['embeddeds'][] = $this->parseEmbeddedClass($name, $annotation, $entityMetadata);
                }

                // relation
                if ($annotation instanceof \ProAI\Datamapper\Annotations\Relation) {
                    $entityMetadata['relations'][] = $this->parseRelation($name, $annotation, $entityMetadata);
                }
            }
        }

        // check timestamps extension
        if (! empty($entityMetadata['timestamps'])) {
            $this->validator->validateTimestamps($entityMetadata);
        }

        // check softDeletes extension
        if (! empty($entityMetadata['softDeletes'])) {
            $this->validator->validateSoftDeletes($entityMetadata);
        }

        // check version extension
        if (! empty($entityMetadata['versionTable'])) {
            $this->validator->validateVersionTable($entityMetadata);
        }

        return $entityMetadata;
    }
597379
public function fetchAllObj(string $class = null): array
    {
        if ($class == null && $this->modelClass !== null) {
            return parent::fetchAllObj($this->modelClass);
        } else {
            return parent::fetchAllObj($class);
        }
    }
396014
private function getVersions($class)
    {
        $versions = [
            'versions' => [
                '1.0' => $class
            ],
            'latest'   => '1.0',
            'current'  => '1.0'
        ];

        if (method_exists($class, 'getAllVersions')) {
            // all versions
            $interfaceVersions = $class::getAllVersions();
            if (count($interfaceVersions) > 0) {
                $versions = ['versions' => $interfaceVersions];
            }

            // current
            $interfaceCurrent = $class::getCurrentVersion();
            if (!empty($interfaceCurrent)) {
                $versions['current'] = $interfaceCurrent;
            } else {
                $ak = array_keys($versions['versions']);
                $versions['current'] = end($ak);
            }

            // latest
            $interfaceLatest = $class::getLatestVersion();
            if (!empty($interfaceLatest)) {
                $versions['latest'] = $interfaceLatest;
            } else {
                $ak = array_keys($versions['versions']);
                $versions['latest'] = end($ak);
            }
        }

        return $versions;
    }
422888
public function getDescendantsOf($class)
    {
        if (is_object($class)) {
            $class = get_class($class);
        }

        $lClass = strtolower($class);
        if (array_key_exists($lClass, $this->descendants)) {
            return $this->descendants[$lClass];
        }

        return [];
    }
619940
private function parseClass()
    {
        // check which interfaces are implemented
        $interfaces = [];
        foreach ($this->parentClasses as $pc) {
            $interfaces = array_merge($interfaces,
                $pc->getInterfaceNames()); // returns only the interfaces that the current class implements
        }
        $interfaces = array_unique($interfaces);

        foreach ($interfaces as $i) {
            if ($i == AccessInterface::class) {
                $this->parsedClass->accessInterface = true;
            }

            if ($i == CacheKeyInterface::class) {
                $this->parsedClass->cacheKeyInterface = true;
            }
        }
    }