public function handle(CodeGeneratorRequest $request) { $files = array(); foreach ((array) $request->getProtoFile() as $file) { /** @var FileDescriptorProto $file */ $this->collectFile($file, array()); $specClassName = "Skrz\\Meta\\MetaSpec"; $customSpecClassName = false; foreach ($file->getSourceCodeInfo()->getLocation() as $location) { if ($location->getPath() === array(FileDescriptorProtoMeta::PACKAGE_PROTOBUF_FIELD) && $location->getLeadingComments() && preg_match("/@spec\\s+([a-zA-Z0-9_\\\\]+)/", $location->getLeadingComments(), $m)) { $specClassName = $m[1]; $customSpecClassName = true; } } uksort($this->messages, function ($a, $b) { if (strlen($a) === strlen($b)) { return strcmp($b, $a); } return strlen($b) - strlen($a); }); $tmpFiles = array(); foreach ($this->messages as $className => $message) { // compile message file $codeFile = new CodeGeneratorResponse\File(); $codeFile->setName(str_replace("\\", "/", $className) . ".php"); $result = $this->generateMessage($className, $message); $codeFile->setContent((string) $result->getFile()); $files[$className] = $codeFile; // compile meta file if (!class_exists($className)) { $tmpFiles[] = $tmpFile = tempnam(sys_get_temp_dir(), "protoc-gen-php"); file_put_contents($tmpFile, (string) $result->getFile()); require_once $tmpFile; } /** @var AbstractMetaSpec $spec */ $spec = new $specClassName(); if (!$customSpecClassName) { $spec->match($className)->addModule(new ProtobufModule()); } $metaResult = $spec->compile($type = Type::fromString($className)); if ($metaResult !== null) { $metaFile = new CodeGeneratorResponse\File(); $metaFile->setName(str_replace("\\", "/", $spec->createMetaClassName($type)) . ".php"); $metaFile->setContent((string) $metaResult->getFile()); $files[$metaResult->getClass()->getName()] = $metaFile; } } foreach ($tmpFiles as $tmpFile) { unlink($tmpFile); } foreach ($this->enums as $className => $enum) { $enumFile = new CodeGeneratorResponse\File(); $enumFile->setName(str_replace("\\", "/", $className) . ".php"); $result = $this->generateEnum($className, $enum); $enumFile->setContent((string) $result->getFile()); $files[$className] = $enumFile; } } $response = new CodeGeneratorResponse(); $response->setFile($files); return $response; }
public function onGenerate(AbstractMetaSpec $spec, MetaSpecMatcher $matcher, Type $type, ClassType $class) { $groups = array(); $inputOutputClasses = array($type->getName() => true); $i = 0; foreach ($this->defaultGroups as $defaultGroup) { $groups[$defaultGroup] = 1 << $i++; } $ns = $class->getNamespace(); $ns->addUse("Skrz\\Meta\\PHP\\PhpMetaInterface"); $ns->addUse($type->getName(), null, $typeAlias); $ns->addUse("Skrz\\Meta\\Stack", null, $stackAlias); $class->addImplement("Skrz\\Meta\\PHP\\PhpMetaInterface"); // get groups foreach ($type->getProperties() as $property) { foreach ($property->getAnnotations("Skrz\\Meta\\PHP\\PhpArrayOffset") as $arrayOffset) { /** @var PhpArrayOffset $arrayOffset */ if (!isset($groups[$arrayOffset->group])) { $groups[$arrayOffset->group] = 1 << $i++; } } } // get discriminator $discriminatorOffsetMap = array(); $discriminatorClassMap = array(); $discriminatorMetaMap = array(); foreach ($type->getAnnotations("Skrz\\Meta\\PHP\\PhpDiscriminatorOffset") as $discriminatorOffset) { /** @var PhpDiscriminatorOffset $discriminatorOffset */ if (!isset($groups[$discriminatorOffset->group])) { $groups[$discriminatorOffset->group] = 1 << $i++; } $discriminatorOffsetMap[$groups[$discriminatorOffset->group]] = $discriminatorOffset->offset; } foreach ($type->getAnnotations("Skrz\\Meta\\PHP\\PhpDiscriminatorMap") as $discriminatorMap) { /** @var PhpDiscriminatorMap $discriminatorMap */ if (!isset($groups[$discriminatorMap->group])) { $groups[$discriminatorMap->group] = 1 << $i++; } if (isset($discriminatorMetaMap[$groups[$discriminatorMap->group]])) { throw new MetaException("More @PhpDiscriminatorMap annotations with same group '{$discriminatorMap->group}'."); } $discriminatorClassMap[$groups[$discriminatorMap->group]] = array(); $discriminatorMetaMap[$groups[$discriminatorMap->group]] = array(); $currentClassMap =& $discriminatorClassMap[$groups[$discriminatorMap->group]]; $currentMetaMap =& $discriminatorMetaMap[$groups[$discriminatorMap->group]]; foreach ($discriminatorMap->map as $value => $className) { $currentClassMap[$value] = $className; $inputOutputClasses[$className] = true; $currentMetaMap[$value] = $spec->createMetaClassName(Type::fromString($className)); } } // add groups property $groupsProperty = $class->addProperty("groups"); $groupsProperty->setStatic(true)->setValue($groups)->setVisibility("private"); $groupsProperty->addComment("Mapping from group name to group ID for fromArray() and toArray()")->addComment("")->addComment("@var string[]"); // create input/output type hint $inputOutputTypeHint = array(); $inputOutputClasses = array_keys($inputOutputClasses); sort($inputOutputClasses); foreach ($inputOutputClasses as $inputOutputClass) { $ns->addUse($inputOutputClass, null, $alias); $inputOutputTypeHint[] = $alias; } $inputOutputTypeHint = implode("|", $inputOutputTypeHint); foreach (array("Array", "Object") as $what) { // from*() method $from = $class->addMethod("from{$what}"); $from->setStatic(true); $from->addParameter("input"); $from->addParameter("group")->setOptional(true); $from->addParameter("object")->setOptional(true); $from->addComment("Creates \\{$type->getName()} object from " . strtolower($what))->addComment("")->addComment("@param " . strtolower($what) . " \$input")->addComment("@param string \$group")->addComment("@param {$inputOutputTypeHint} \$object")->addComment("")->addComment("@throws \\Exception")->addComment("")->addComment("@return {$inputOutputTypeHint}"); if ($what === "Object") { $from->addBody("\$input = (array)\$input;\n"); } // TODO: more groups - include/exclude $from->addBody("if (!isset(self::\$groups[\$group])) {")->addBody("\tthrow new \\InvalidArgumentException('Group \\'' . \$group . '\\' not supported for ' . " . var_export($type->getName(), true) . " . '.');")->addBody("} else {")->addBody("\t\$id = self::\$groups[\$group];")->addBody("}")->addBody(""); if (!empty($discriminatorMetaMap)) { foreach ($discriminatorMetaMap as $groupId => $groupDiscriminatorMetaMap) { if (isset($discriminatorOffsetMap[$groupId])) { $groupDiscriminatorOffset = $discriminatorOffsetMap[$groupId]; foreach ($groupDiscriminatorMetaMap as $value => $metaClass) { $ns->addUse($metaClass, null, $alias); $from->addBody("if ((\$id & {$groupId}) > 0 && " . "isset(\$input[" . var_export($groupDiscriminatorOffset, true) . "]) && " . "\$input[" . var_export($groupDiscriminatorOffset, true) . "] === " . var_export($value, true) . ") {")->addBody("\treturn {$alias}::from{$what}(\$input, \$group, \$object);")->addBody("}")->addBody(""); } } else { foreach ($groupDiscriminatorMetaMap as $value => $metaClass) { $ns->addUse($metaClass, null, $alias); $from->addBody("if ((\$id & {$groupId}) > 0 && " . "isset(\$input[" . var_export($value, true) . "])) {")->addBody("\treturn {$alias}::from{$what}(\$input[" . var_export($value, true) . "], \$group, \$object);")->addBody("}")->addBody(""); } } } } $from->addBody("if (\$object === null) {")->addBody("\t\$object = new {$typeAlias}();")->addBody("} elseif (!(\$object instanceof {$typeAlias})) {")->addBody("\tthrow new \\InvalidArgumentException('You have to pass object of class {$type->getName()}.');")->addBody("}")->addBody(""); foreach ($type->getProperties() as $property) { foreach ($property->getAnnotations("Skrz\\Meta\\PHP\\PhpArrayOffset") as $arrayOffset) { /** @var PhpArrayOffset $arrayOffset */ $groupId = $groups[$arrayOffset->group]; $arrayKey = var_export($arrayOffset->offset, true); $baseArrayPath = $arrayPath = "\$input[{$arrayKey}]"; $baseObjectPath = $objectPath = "\$object->{$property->getName()}"; $from->addBody("if ((\$id & {$groupId}) > 0 && isset({$arrayPath})) {"); // FIXME: group group IDs by offset $baseType = $property->getType(); $indent = "\t"; $before = ""; $after = ""; for ($i = 0; $baseType instanceof ArrayType; ++$i) { $arrayType = $baseType; $baseType = $arrayType->getBaseType(); $before .= "{$indent}if (!(isset({$objectPath}) && is_array({$objectPath}))) {\n"; $before .= "{$indent}\t{$objectPath} = array();\n"; $before .= "{$indent}}\n"; $before .= "{$indent}foreach ({$arrayPath} instanceof \\Traversable ? {$arrayPath} : (array){$arrayPath} as \$k{$i} => \$v{$i}) {\n"; $after = "{$indent}}\n" . $after; $indent .= "\t"; $arrayPath = "\$v{$i}"; $objectPath .= "[\$k{$i}]"; } if (!empty($before)) { $from->addBody(rtrim($before)); } $matchingPropertySerializer = $this->getMatchingPropertySerializer($property, $arrayOffset); if ($matchingPropertySerializer !== null) { $sevo = $matchingPropertySerializer->deserialize($property, $arrayOffset->group, $arrayPath); if ($sevo->getStatement()) { $from->addBody(Strings::indent($sevo->getStatement(), 1, $indent)); } $from->addBody("{$indent}{$objectPath} = {$sevo->getExpression()};"); } elseif ($baseType instanceof ScalarType) { $from->addBody("{$indent}{$objectPath} = {$arrayPath};"); } elseif ($baseType instanceof Type) { $propertyTypeMetaClassName = $spec->createMetaClassName($baseType); $ns->addUse($propertyTypeMetaClassName, null, $propertyTypeMetaClassNameAlias); $from->addBody("{$indent}{$objectPath} = {$propertyTypeMetaClassNameAlias}::from{$what}(" . "{$arrayPath}, " . "\$group, " . "isset({$objectPath}) ? {$objectPath} : null" . ");"); } else { throw new MetaException("Unsupported property type " . get_class($baseType) . " ({$type->getName()}::\${$property->getName()})."); } if (!empty($after)) { $from->addBody(rtrim($after)); } $from->addBody("} elseif ((\$id & {$groupId}) > 0 && array_key_exists({$arrayKey}, \$input) && {$baseArrayPath} === null) {")->addBody("\t{$baseObjectPath} = null;")->addBody("}"); } $from->addBody(""); } $from->addBody("return \$object;"); // to*() method $to = $class->addMethod("to{$what}"); $to->setStatic(true); $to->addParameter("object"); $to->addParameter("group")->setOptional(true); $to->addParameter("filter")->setOptional(true); $to->addComment("Serializes \\{$type->getName()} to " . strtolower($what))->addComment("")->addComment("@param {$inputOutputTypeHint} \$object")->addComment("@param string \$group")->addComment("@param array \$filter")->addComment("")->addComment("@throws \\Exception")->addComment("")->addComment("@return " . strtolower($what)); $to->addBody("if (\$object === null) {")->addBody("\treturn null;")->addBody("}"); // TODO: more groups - include/exclude $to->addBody("if (!isset(self::\$groups[\$group])) {")->addBody("\tthrow new \\InvalidArgumentException('Group \\'' . \$group . '\\' not supported for ' . " . var_export($type->getName(), true) . " . '.');")->addBody("} else {")->addBody("\t\$id = self::\$groups[\$group];")->addBody("}")->addBody(""); if (!empty($discriminatorClassMap)) { foreach ($discriminatorClassMap as $groupId => $groupDiscriminatorClassMap) { $groupDiscriminatorOffset = null; if (isset($discriminatorOffsetMap[$groupId])) { $groupDiscriminatorOffset = $discriminatorOffsetMap[$groupId]; } foreach ($groupDiscriminatorClassMap as $value => $className) { $metaClassName = $discriminatorMetaMap[$groupId][$value]; $ns->addUse($className, null, $alias); $ns->addUse($metaClassName, null, $metaAlias); $to->addBody("if ((\$id & {$groupId}) > 0 && \$object instanceof {$alias}) {")->addBody("\t\$output = {$metaAlias}::to{$what}(\$object, \$group);"); if ($groupDiscriminatorOffset === null) { $to->addBody("\t\$output = " . ($what === "Object" ? "(object)" : "") . "array(" . var_export($value, true) . " => " . ($what === "Object" ? "(object)" : "") . "\$output);"); } else { if ($what === "Object") { $to->addBody("\t\$output->{$groupDiscriminatorOffset} = " . var_export($value, true) . ";"); // FIXME: might compile to incorrect PHP code } else { $to->addBody("\t\$output[" . var_export($groupDiscriminatorOffset, true) . "] = " . var_export($value, true) . ";"); } } $to->addBody("\treturn \$output;")->addBody("}")->addBody(""); } } } $to->addBody("if (!(\$object instanceof {$typeAlias})) {")->addBody("\tthrow new \\InvalidArgumentException('You have to pass object of class {$type->getName()}.');")->addBody("}")->addBody(""); $to->addBody("if ({$stackAlias}::\$objects === null) {")->addBody("\t{$stackAlias}::\$objects = new \\SplObjectStorage();")->addBody("}")->addBody("")->addBody("if ({$stackAlias}::\$objects->contains(\$object)) {")->addBody("\treturn null;")->addBody("}")->addBody("")->addBody("{$stackAlias}::\$objects->attach(\$object);")->addBody(""); $to->addBody("try {")->addBody("\t\$output = array();")->addBody(""); foreach ($type->getProperties() as $property) { $propertyGroups = []; foreach ($property->getAnnotations("Skrz\\Meta\\PHP\\PhpArrayOffset") as $arrayOffset) { if (isset($propertyGroups[$arrayOffset->group])) { continue; } $propertyGroups[$arrayOffset->group] = true; /** @var PhpArrayOffset $arrayOffset */ $groupId = $groups[$arrayOffset->group]; $if = "\tif ((\$id & {$groupId}) > 0"; if ($arrayOffset->ignoreNull) { $if .= " && ((isset(\$object->{$property->getName()}) && \$filter === null)"; } else { $if .= " && (\$filter === null"; } $if .= " || isset(\$filter[" . var_export($arrayOffset->offset, true) . "]))) {"; // FIXME: group group IDs by offset $to->addBody($if); $objectPath = "\$object->{$property->getName()}"; $arrayPath = "\$output[" . var_export($arrayOffset->offset, true) . "]"; $baseType = $property->getType(); $indent = "\t\t"; $before = ""; $after = ""; for ($i = 0; $baseType instanceof ArrayType; ++$i) { $arrayType = $baseType; $baseType = $arrayType->getBaseType(); $before .= "{$indent}if (!(isset({$arrayPath}) && is_array({$arrayPath}))) {\n"; $before .= "{$indent}\t{$arrayPath} = array();\n"; $before .= "{$indent}}\n"; $before .= "{$indent}foreach ({$objectPath} instanceof \\Traversable ? {$objectPath} : (array){$objectPath} as \$k{$i} => \$v{$i}) {\n"; $after = "{$indent}}\n" . $after; $indent .= "\t"; $arrayPath .= "[\$k{$i}]"; $objectPath = "\$v{$i}"; } if (!empty($before)) { $to->addBody(rtrim($before)); } $matchingPropertySerializer = $this->getMatchingPropertySerializer($property, $arrayOffset); if ($matchingPropertySerializer !== null) { $sevo = $matchingPropertySerializer->serialize($property, $arrayOffset->group, $objectPath); if ($sevo->getStatement()) { $to->addBody(Strings::indent($sevo->getStatement(), 1, $indent)); } $to->addBody("{$indent}{$arrayPath} = {$sevo->getExpression()};"); } elseif ($baseType instanceof ScalarType) { $to->addBody("{$indent}{$arrayPath} = {$objectPath};"); } elseif ($baseType instanceof Type) { $propertyTypeMetaClassName = $spec->createMetaClassName($baseType); $ns->addUse($propertyTypeMetaClassName, null, $propertyTypeMetaClassNameAlias); $to->addBody("{$indent}{$arrayPath} = {$propertyTypeMetaClassNameAlias}::to{$what}(" . "{$objectPath}, " . "\$group, " . "\$filter === null ? null : \$filter[" . var_export($arrayOffset->offset, true) . "]" . ");"); } else { throw new MetaException("Unsupported property type " . get_class($baseType) . "."); } if (!empty($after)) { $to->addBody(rtrim($after)); } $to->addBody("\t}"); } $to->addBody(""); } $to->addBody("} catch (\\Exception \$e) {")->addBody("\t{$stackAlias}::\$objects->detach(\$object);")->addBody("\tthrow \$e;")->addBody("}")->addBody(""); $to->addBody("{$stackAlias}::\$objects->detach(\$object);")->addBody("return " . ($what === "Object" ? "(object)" : "") . "\$output;"); } }