public function generate(array $config)
{
$baseClass = ['constants' => [], 'fields' => [], 'uses' => [], 'hasConstructor' => false, 'parentHasConstructor' => false, 'hasChild' => false, 'abstract' => false];
$typesToGenerate = [];
if (empty($config['types'])) {
foreach ($this->graphs as $graph) {
$typesToGenerate = $graph->allOfType('rdfs:Class');
}
} else {
foreach ($config['types'] as $typeName => $typeConfig) {
$resource = null;
foreach ($this->graphs as $graph) {
$resources = $graph->resources();
if (isset($resources[$typeConfig['vocabularyNamespace'] . $typeName])) {
$resource = $graph->resource($typeConfig['vocabularyNamespace'] . $typeName, 'rdfs:Class');
break;
}
}
if ($resource) {
$typesToGenerate[$typeName] = $resource;
} else {
$this->logger->warning('Type "{typeName}" cannot be found. Using "{guessFrom}" type to generate entity.', ['typeName' => $typeName, 'guessFrom' => $typeConfig['guessFrom']]);
$type = $graph->resource($typeConfig['vocabularyNamespace'] . $typeConfig['guessFrom'], 'rdfs:Class');
$typesToGenerate[$typeName] = $type;
}
}
}
$classes = [];
$propertiesMap = $this->createPropertiesMap($typesToGenerate);
foreach ($typesToGenerate as $typeName => $type) {
$typeName = is_string($typeName) ? $typeName : $type->localName();
$typeConfig = isset($config['types'][$typeName]) ? $config['types'][$typeName] : null;
$class = $baseClass;
$comment = $type->get('rdfs:comment');
$class['name'] = $typeName;
$class['label'] = $comment ? $comment->getValue() : '';
$class['resource'] = $type;
$class['config'] = $typeConfig;
$class['isEnum'] = $this->isEnum($type);
if ($class['isEnum']) {
$class['namespace'] = isset($typeConfig['namespace']) ? $typeConfig['namespace'] : $config['namespaces']['enum'];
$class['parent'] = self::ENUM_EXTENDS;
$class['uses'][] = self::ENUM_USE;
// Constants
foreach ($this->graphs as $graph) {
foreach ($graph->allOfType($type->getUri()) as $instance) {
$class['constants'][$instance->localName()] = ['name' => strtoupper(substr(preg_replace('/([A-Z])/', '_$1', $instance->localName()), 1)), 'resource' => $instance, 'value' => $instance->getUri()];
}
}
} else {
// Entities
$class['namespace'] = isset($typeConfig['namespaces']['class']) ? $typeConfig['namespaces']['class'] : $config['namespaces']['entity'];
// Parent
$class['parent'] = isset($typeConfig['parent']) ? $typeConfig['parent'] : null;
if (null === $class['parent']) {
$numberOfSupertypes = count($type->all('rdfs:subClassOf'));
if ($numberOfSupertypes > 1) {
$this->logger->warning(sprintf('The type "%s" has several supertypes. Using the first one.', $type->localName()));
}
$class['parent'] = $numberOfSupertypes ? $type->all('rdfs:subClassOf')[0]->localName() : false;
}
if (isset($class['parent']) && isset($config['types'][$class['parent']]['namespaces']['class'])) {
$parentNamespace = $config['types'][$class['parent']]['namespaces']['class'];
if ($parentNamespace !== $class['namespace']) {
$class['uses'][] = $parentNamespace . '\\' . $class['parent'];
}
}
// Embeddable
$class['embeddable'] = isset($typeConfig['embeddable']) ? $typeConfig['embeddable'] : false;
if (!empty($config['types']) && $class['parent'] && !isset($config['types'][$class['parent']])) {
$this->logger->error(sprintf('The type "%s" (parent of "%s") doesn\'t exist', $class['parent'], $type->localName()));
}
// Interfaces
if ($config['useInterface']) {
$class['interfaceNamespace'] = isset($typeConfig['namespaces']['interface']) && $typeConfig['namespaces']['interface'] ? $typeConfig['namespaces']['interface'] : $config['namespaces']['interface'];
$class['interfaceName'] = sprintf('%sInterface', $typeName);
}
}
// Fields
if (!$typeConfig['allProperties'] && isset($typeConfig['properties']) && is_array($typeConfig['properties'])) {
foreach ($typeConfig['properties'] as $key => $value) {
foreach ($propertiesMap[$type->getUri()] as $property) {
if ($key !== $property->localName()) {
continue;
}
$class = $this->generateField($config, $class, $type, $typeName, $property->localName(), $property);
continue 2;
}
// Add custom fields (non schema.org)
$this->logger->info(sprintf('The property "%s" (type "%s") is a custom property.', $key, $type->localName()));
$class = $this->generateField($config, $class, $type, $typeName, $key);
}
} else {
// All properties
foreach ($propertiesMap[$type->getUri()] as $property) {
$class = $this->generateField($config, $class, $type, $typeName, $property->localName(), $property);
}
}
$classes[$typeName] = $class;
}
// Second pass
foreach ($classes as &$class) {
if ($class['parent'] && isset($classes[$class['parent']])) {
$classes[$class['parent']]['hasChild'] = true;
$class['parentHasConstructor'] = $classes[$class['parent']]['hasConstructor'];
}
foreach ($class['fields'] as &$field) {
$field['isEnum'] = isset($classes[$field['range']]) && $classes[$field['range']]['isEnum'];
}
}
// Third pass
foreach ($classes as &$class) {
if (isset($config['types'][$class['name']]['abstract']) && null !== $config['types'][$class['name']]['abstract']) {
$class['abstract'] = $config['types'][$class['name']]['abstract'];
} else {
$class['abstract'] = $class['hasChild'];
}
// When including all properties, ignore properties already set on parent
if (isset($config['types'][$class['name']]['allProperties']) && $config['types'][$class['name']]['allProperties'] && isset($classes[$class['parent']])) {
$type = $class['resource'];
foreach ($propertiesMap[$type->getUri()] as $property) {
if (!isset($class['fields'][$property->localName()])) {
continue;
}
$parentConfig = isset($config['types'][$class['parent']]) ? $config['types'][$class['parent']] : null;
$parentClass = $classes[$class['parent']];
while ($parentClass) {
if (!isset($parentConfig['properties']) || !is_array($parentConfig['properties'])) {
// Unset implicit property
$parentType = $parentClass['resource'];
if (in_array($property, $propertiesMap[$parentType->getUri()])) {
unset($class['fields'][$property->localName()]);
continue 2;
}
} else {
// Unset explicit property
if (array_key_exists($property->localName(), $parentConfig['properties'])) {
unset($class['fields'][$property->localName()]);
continue 2;
}
}
$parentConfig = $parentClass['parent'] ? isset($config['types'][$parentClass['parent']]) ? $config['types'][$parentClass['parent']] : null : null;
$parentClass = $parentClass['parent'] ? $classes[$parentClass['parent']] : null;
}
}
}
}
// Generate ID
if ($config['generateId']) {
foreach ($classes as &$class) {
if (!$class['hasChild'] && !$class['isEnum'] && !$class['embeddable']) {
$class['fields'] = ['id' => ['name' => 'id', 'resource' => null, 'range' => 'Integer', 'cardinality' => CardinalitiesExtractor::CARDINALITY_1_1, 'ormColumn' => null, 'isArray' => false, 'isNullable' => false, 'isUnique' => false, 'isCustom' => true, 'isEnum' => false, 'isId' => true]] + $class['fields'];
}
}
}
// Initialize annotation generators
$annotationGenerators = [];
foreach ($config['annotationGenerators'] as $annotationGenerator) {
$generator = new $annotationGenerator($this->logger, $this->graphs, $this->cardinalities, $config, $classes);
$annotationGenerators[] = $generator;
}
$interfaceMappings = [];
$generatedFiles = [];
foreach ($classes as $className => &$class) {
$class['uses'] = $this->generateClassUses($annotationGenerators, $classes, $className);
$class['annotations'] = $this->generateClassAnnotations($annotationGenerators, $className);
if (false === isset($typesToGenerate[$className])) {
$class['interfaceAnnotations'] = $this->generateInterfaceAnnotations($annotationGenerators, $className);
}
foreach ($class['constants'] as $constantName => $constant) {
$class['constants'][$constantName]['annotations'] = $this->generateConstantAnnotations($annotationGenerators, $className, $constantName);
}
foreach ($class['fields'] as $fieldName => &$field) {
$typeHint = false;
if ($this->isDateTime($field['range'])) {
$typeHint = '\\DateTime';
} elseif (!($this->isDatatype($field['range']) || $field['isEnum'])) {
if (isset($classes[$field['range']]['interfaceName'])) {
$typeHint = $classes[$field['range']]['interfaceName'];
} else {
$typeHint = $classes[$field['range']]['name'];
}
}
$field['typeHint'] = $typeHint;
$field['annotations'] = $this->generateFieldAnnotations($annotationGenerators, $className, $fieldName);
$field['getterAnnotations'] = $this->generateGetterAnnotations($annotationGenerators, $className, $fieldName);
if ($field['isArray']) {
$field['adderAnnotations'] = $this->generateAdderAnnotations($annotationGenerators, $className, $fieldName);
$field['removerAnnotations'] = $this->generateRemoverAnnotations($annotationGenerators, $className, $fieldName);
} else {
$field['setterAnnotations'] = $this->generateSetterAnnotations($annotationGenerators, $className, $fieldName);
}
}
$classDir = $this->namespaceToDir($config, $class['namespace']);
if (!file_exists($classDir)) {
mkdir($classDir, 0777, true);
}
$path = sprintf('%s%s.php', $classDir, $className);
$generatedFiles[] = $path;
file_put_contents($path, $this->twig->render('class.php.twig', ['config' => $config, 'class' => $class]));
if (isset($class['interfaceNamespace'])) {
$interfaceDir = $this->namespaceToDir($config, $class['interfaceNamespace']);
if (!file_exists($interfaceDir)) {
mkdir($interfaceDir, 0777, true);
}
$path = sprintf('%s%s.php', $interfaceDir, $class['interfaceName']);
$generatedFiles[] = $path;
file_put_contents($path, $this->twig->render('interface.php.twig', ['config' => $config, 'class' => $class]));
if ($config['doctrine']['resolveTargetEntityConfigPath'] && !$class['abstract']) {
$interfaceMappings[$class['interfaceNamespace'] . '\\' . $class['interfaceName']] = $class['namespace'] . '\\' . $className;
}
}
}
if (!empty($interfaceMappings) && $config['doctrine']['resolveTargetEntityConfigPath']) {
$file = $config['output'] . '/' . $config['doctrine']['resolveTargetEntityConfigPath'];
$dir = dirname($file);
if (!file_exists($dir)) {
mkdir($dir, 0777, true);
}
file_put_contents($file, $this->twig->render('doctrine.xml.twig', ['mappings' => $interfaceMappings]));
$generatedFiles[] = $file;
}
$this->fixCs($generatedFiles);
}