protected function execute(InputInterface $input, OutputInterface $output) { $config = $this->getApplication()->getConfig(); $dialog = $this->getHelper('dialog'); $className = $input->getArgument('className'); $modelName = $input->getArgument('modelName'); $endPoint = $input->getArgument('endPoint'); $model = $this->getModel($modelName); $buildDirectory = $config['build']['classes']; $buildPath = $buildDirectory . '/' . $className . '.php'; if (file_exists($buildPath)) { if (!$dialog->askConfirmation($output, sprintf('<question>Class file "%s" exists, overwrite?</question>', $buildPath), false)) { return; } } $modelConfig = ['properties' => $model->properties]; $configsDirectory = $config['build']['configs']; $configPath = realpath($configsDirectory . '/' . $modelName . '.json'); if (file_exists($configPath)) { $modelConfig = json_decode(file_get_contents($configPath), true); } $namespace = new PhpNamespace($config['namespace']); $namespace->addUse($config['extends']); $class = new ClassType($className, $namespace); $class->addExtend($config['extends']); if (!empty($endPoint)) { $class->addConst("ENDPOINT", $endPoint); } foreach ($model->properties as $propertyName => $propertyDef) { if (in_array($propertyName, $modelConfig['properties'], true)) { $property = $class->addProperty($propertyName)->setVisibility('public'); $accessorMethod = $class->addMethod($this->toCamelCase("get_" . $propertyName)); $accessorMethod->setBody('return $this->' . $propertyName . ';'); $mutatorMethod = $class->addMethod($this->toCamelCase("set_" . $propertyName)); $mutatorMethod->addParameter($propertyName); $mutatorMethod->setBody('$this->' . $propertyName . ' = $' . $propertyName . ';'); if (is_string($propertyDef['type'])) { $property->addDocument("@var {$propertyDef['type']}"); } else { $property->addDocument("@var mixed"); } } else { $output->writeln(sprintf("<info>Skipped property %s</info>", $propertyName)); } } file_put_contents($buildPath, str_replace("\t", " ", "<?php\n{$namespace}{$class}")); // TODO: replace with PHP_CodeSniffer library exec(sprintf('vendor/bin/phpcbf --standard=PSR2 --encoding=utf-8 "%s"', $buildPath)); $output->writeln(sprintf("<info>Class %s created</info>", $buildPath)); }
/** * [generateClassType description]. * * @param string $properties elementi possibili 'fields', 'extend', 'implements' * @param array $typesReference [description] * @param array $typesDescription [description] * @param ClassConfig $config [description] */ public function generateClassType(array $properties, $typesReference, $typesDescription, ClassConfig $config) { $phpNamespace = $this->currentClass->getNamespace(); if ($config->isInterface) { $this->info('Passo a interfaccia', [$this->currentClass->getName()]); $docs = $this->currentClass->getComment(); $this->currentClass = $this->currentFile->addInterface($phpNamespace->getName() . '\\' . ucfirst($this->currentClass->getName())); $this->currentClass->setComment($docs); $this->info('Check haveConstructor, in caso metto a false', [$config->haveConstructor]); $config->haveConstructor = false; } $this->info('Generate', ['class' => $this->currentClass->getName(), 'namespace' => $phpNamespace->getName(), 'comment' => $this->currentClass->getComment(), 'properties' => $properties]); // extend class if (array_key_exists('extend', $properties)) { $extendClassName = $properties['extend']; $this->info('Aggiungo extend', ['class' => $this->currentClass->getName(), 'extend' => $extendClassName]); $this->currentClass->setExtends($extendClassName); $this->currentClass->getNamespace()->addUse($extendClassName); } // implements class if (array_key_exists('implements', $properties)) { $implementsList = []; if (!is_array($properties['implements'])) { $implementsList[] = $properties['implements']; } else { $implementsList = array_merge($implementsList, $properties['implements']); } $this->currentClass->setImplements($implementsList); foreach ($implementsList as $implementUse) { $this->info('Aggiungo implement', ['class' => $this->currentClass->getName(), 'implements' => $implementUse]); $this->currentClass->getNamespace()->addUse($implementUse); } } // traits if (array_key_exists('traits', $properties)) { if (is_array($properties['traits'])) { foreach ($properties['traits'] as $trait) { $this->addTrait($trait, $typesReference); } } else { $traitObject = $properties['traits']; $this->addTrait($traitObject, $typesReference); } } if ($config->isFinalClass) { $this->currentClass->setFinal(true); } $first = true; if (array_key_exists('fields', $properties)) { /** @var $methodConstructor \Nette\PhpGenerator\Method */ $methodConstructor = null; if ($config->haveConstructor) { $methodConstructor = $this->addConstructor(); } $body = ''; foreach ($properties['fields'] as $name => $fieldProperties) { $isStatic = false; $isAutoinizialize = false; $defaultValue = null; if (array_key_exists('static', $fieldProperties)) { $isStatic = $fieldProperties['static']; } if (array_key_exists('autoinizialize', $fieldProperties)) { $isAutoinizialize = boolval($fieldProperties['autoinizialize']); } if (array_key_exists('default', $fieldProperties)) { $defaultValue = $fieldProperties['default']; } if (!$isAutoinizialize) { if (null != $defaultValue) { //TODO: usare "primitive type per determinare il corretto IF" //FARE UN TEST PER I BOOLEAN //@see https://www.virendrachandak.com/techtalk/php-isset-vs-empty-vs-is_null/ $body .= 'if ( empty($' . $name . ') ) { ' . "\n"; if ($isStatic) { $body .= ' self::$'; } else { $body .= ' $this->'; } $body .= $name . ' = ' . $defaultValue . ';' . "\n"; $body .= '} else {'; if ($isStatic) { $body .= ' self::$'; } else { $body .= ' $this->'; } $body .= $name . ' = $' . $name . ';' . "\n"; $body .= '}' . "\n"; } else { if (!$isStatic) { $body .= ' $this->' . $name . ' = $' . $name . ';' . "\n"; } } } else { if (!empty($defaultValue) || is_int($defaultValue)) { if (substr(rtrim($defaultValue), -1) == ';') { $this->error('autoinizialize for ' . $name . ' on class ' . $this->currentClass->getName() . ' have default with ";" please remove!'); $defaultValue = substr($defaultValue, 0, strlen($defaultValue) - 1); } if (!$isStatic) { if ($isAutoinizialize) { $body .= '// autoinizialize' . "\n"; $body .= '$this->' . $name . ' = ' . $defaultValue . ';' . "\n"; } else { if ($defaultValue) { $body .= 'if ( !is_null($' . $name . ') ) {' . "\n"; $body .= ' $this->' . $name . ' = $' . $name . ';' . "\n"; $body .= '} else {' . "\n"; $body .= ' $this->' . $name . ' = ' . $defaultValue . ';' . "\n"; $body .= '}' . "\n"; // $body .= '$this->'.$name.' = '.$defaultValue.';'."\n"; } else { $body .= 'if ( is_null($' . $name . ') ) {' . "\n"; $body .= ' $this->' . $name . ' = ' . $defaultValue . ';' . "\n"; $body .= '}' . "\n"; } } } } else { $this->error('autoinizialize for ' . $name . ' not defined on element ' . $this->currentClass->getName()); $this->errors[] = 'autoinizialize for ' . $name . ' not defined on element ' . $this->currentClass->getName(); } } $fieldClassFull = ''; if (array_key_exists('class', $fieldProperties)) { $fieldClassName = ucfirst($fieldProperties['class']); if (array_key_exists($fieldClassName, $typesReference)) { $fieldNamespace = $typesReference[$fieldClassName]; $fieldClassFull = $fieldNamespace . '\\' . $fieldClassName; $this->info('Trovato field namespace tra le reference', ['class' => $this->currentClass->getName(), 'field' => $fieldClassName, 'className' => $fieldClassFull]); } else { //FIXME: strpos is better if ($fieldClassName[0] == '\\') { //Class: \DateTime $fieldClassFull = $fieldClassName; } else { $fieldClassFull = $phpNamespace->getName() . '\\' . $fieldClassName; $this->info('Uso class for field same namespace', ['class' => $this->currentClass->getName(), 'field' => $fieldClassName, 'className' => $fieldClassFull]); } } if ($config->haveConstructor && !$isStatic) { $parameter = null; if (!$isAutoinizialize) { $this->info('Aggiungo parametro al costruttore', ['class' => $this->currentClass->getName(), 'parameter' => $name, 'className' => $fieldClassFull, 'default' => $defaultValue, 'autoinizialize' => $isAutoinizialize]); if (!$first) { $parameter = $methodConstructor->addParameter($name, null); //solo i primitivi hanno un default, gli altri null come object $parameter->setTypeHint($fieldClassFull); } else { $parameter = $methodConstructor->addParameter($name); $parameter->setTypeHint($fieldClassFull); } } else { $this->info('Skip parametro al costruttore -> autoinizialize true', ['class' => $this->currentClass->getName(), 'parameter' => $name, 'className' => $fieldClassFull, 'default' => $defaultValue, 'autoinizialize' => $isAutoinizialize]); } } if (array_key_exists($fieldClassName, $typesReference)) { $this->info('Add field type class with namespace', ['class' => $this->currentClass->getName(), 'field' => $fieldClassName, 'className' => $fieldClassFull]); $this->currentClass->getNamespace()->addUse($fieldClassFull); } } else { //tipo primitivo $fieldClassName = $fieldProperties['primitive']; $fieldNamespace = null; $fieldClassFull = $fieldProperties['primitive']; if ($config->haveConstructor && !$isStatic) { //FIXME: se sono in php7 ho anche gli altri elementi primitivi //@see: http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration $parameter = null; if (!$isAutoinizialize) { if (is_null($defaultValue)) { $this->info('Aggiungo parametro al costruttore', ['class' => $this->currentClass->getName(), 'parameter' => $name, 'className' => $fieldClassFull, 'default' => $defaultValue, 'autoinizialize' => $isAutoinizialize]); //PHP7 ONLY // if ($fieldClassFull == 'int') { // $parameter->setTypeHint('int'); // } if (!$first) { $parameter = $methodConstructor->addParameter($name, null); } else { $parameter = $methodConstructor->addParameter($name); } if ($fieldClassFull == 'array') { $parameter->setTypeHint('array'); } else { if ($defaultValue != null) { /* @var $parameter \Nette\PhpGenerator\Parameter */ $parameter->setDefaultValue('' . $defaultValue); } } } } } } $this->info('Check autoinizialize field', ['class' => $this->currentClass->getName(), 'field' => $name, 'autoinizialize' => $isAutoinizialize, 'default' => $defaultValue]); $comment = 'no description available'; if (array_key_exists('description', $fieldProperties)) { $comment = $fieldProperties['description']; } else { if (!is_null($typesDescription) && array_key_exists($fieldClassName, $typesDescription)) { $comment = $typesDescription[$fieldClassName]; } } if (!$config->isInterface) { /** $field @var \Nette\PhpGenerator\Property */ $field = $this->currentClass->addProperty($name); $field->setStatic($isStatic); if ($config->isEnum) { $field->setVisibility('protected'); } else { $field->setVisibility('private'); } $field->addComment($comment)->addComment('@var ' . $fieldClassFull); } $createSetter = $config->haveSetter; if (array_key_exists('setter', $fieldProperties)) { $createSetter = $fieldProperties['setter']; } $createGetter = $config->haveGetter; if (array_key_exists('getter', $fieldProperties)) { $createGetter = $fieldProperties['getter']; } if ($config->isInterface) { if ($createGetter) { $this->addGetter($name, $fieldClassFull, $isStatic, false); } if ($createSetter) { $this->addSetter($name, $fieldClassFull, $isStatic, false); } } else { if ($createGetter) { $this->addGetter($name, $fieldClassFull, $isStatic, true); } if ($createSetter) { $this->addSetter($name, $fieldClassFull, $isStatic, true); } } if (!$isAutoinizialize) { $first = false; } } if ($config->haveConstructor) { $methodConstructor->setBody($body, []); } } //end fields if (array_key_exists('methods', $properties)) { $body = ''; foreach ($properties['methods'] as $methodName => $methodsProperties) { $this->info('Aggiungo method', ['class' => $this->currentClass->getName(), 'methodName' => $methodName, 'methodProp' => $methodsProperties]); /** $newMethodCall @var \Nette\PhpGenerator\Method */ $newMethodCall = $this->currentClass->addMethod($methodName); $newMethodCall->setFinal(true); $newMethodCall->setStatic(false); if (array_key_exists('static', $methodsProperties)) { $newMethodCall->setStatic($methodsProperties['static']); } if (array_key_exists('description', $methodsProperties)) { $newMethodCall->setVisibility($methodsProperties['visibility']); } else { $newMethodCall->setVisibility('public'); } if (array_key_exists('description', $methodsProperties)) { $newMethodCall->addComment($methodsProperties['description']); } else { $returnType = 'void'; if (array_key_exists('@return', $methodsProperties)) { $returnType = $methodsProperties['@return']; //TODO: .'|null' va messo in quale condizione? $newMethodCall->addComment('@return ' . $returnType); } else { //NOPE } } if (array_key_exists('params', $methodsProperties)) { foreach ($methodsProperties['params'] as $paramName => $paramProp) { if (array_key_exists('class', $paramProp)) { $newMethodCall->addParameter($paramName)->setTypeHint($paramProp['class']); } if (array_key_exists('primitive', $paramProp)) { $newMethodCall->addParameter($paramName); } } } $body = ' // FIMXE: da implementare '; if (array_key_exists('body', $methodsProperties)) { $body = $methodsProperties['body']; } $newMethodCall->setBody($body); } } if ($config->isEnum) { $this->currentClass->setAbstract(true); $this->addSingleton('Singleton instance for enum', false); $this->addParseString(); } if ($config->isSingleton) { $this->addSingleton('Singleton instance', true); } }
/** * @param $className */ private function createRepository($className) { $postfix = 'Repository'; $class = new ClassType($className . $postfix, $this->namespace); $class->setExtends('Nextras\\Orm\\Repository\\Repository'); $class->addMethod("getEntityClassNames")->setStatic(true)->setVisibility('public')->addComment("@return array")->addBody("return [{$className}::class];"); $this->createClass($class, $className); }
public function onGenerate(AbstractMetaSpec $spec, MetaSpecMatcher $matcher, Type $type, ClassType $class) { $ns = $class->getNamespace(); $ns->addUse("Skrz\\Meta\\XML\\XmlMetaInterface"); $ns->addUse($type->getName(), null, $typeAlias); $class->addImplement("Skrz\\Meta\\XML\\XmlMetaInterface"); $groups = array(); $i = 0; $valueGroupIdMask = 0; foreach ($type->getProperties() as $property) { foreach ($property->getAnnotations("Skrz\\Meta\\XML\\XmlAnnotationInterface") as $xmlAnnotation) { /** @var XmlAnnotationInterface $xmlAnnotation */ if (!isset($groups[$xmlAnnotation->getGroup()])) { $groups[$xmlAnnotation->getGroup()] = 1 << $i++; } if ($xmlAnnotation instanceof XmlValue) { $valueGroupIdMask |= $groups[$xmlAnnotation->getGroup()]; } } } $class->addProperty("xmlGroups", $groups)->setStatic(true); // fromXml() $fromXml = $class->addMethod("fromXml"); $fromXml->setStatic(true); $fromXml->addParameter("xml"); $fromXml->addParameter("group")->setOptional(true); $fromXml->addParameter("object")->setOptional(true); $fromXml->addComment("Creates \\{$type->getName()} from XML")->addComment("")->addComment("@param \\XMLReader|\\DOMElement \$xml")->addComment("@param string \$group")->addComment("@param {$typeAlias} \$object")->addComment("")->addComment("@throws \\InvalidArgumentException")->addComment("")->addComment("@return {$typeAlias}"); $fromXml->addBody("if (!isset(self::\$xmlGroups[\$group])) {")->addBody("\tthrow new \\InvalidArgumentException('Group \\'' . \$group . '\\' not supported for ' . " . var_export($type->getName(), true) . " . '.');")->addBody("} else {")->addBody("\t\$id = self::\$xmlGroups[\$group];")->addBody("}")->addBody("")->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("")->addBody("if (\$xml instanceof \\XMLReader) {")->addBody("\treturn self::fromXmlReader(\$xml, \$group, \$id, \$object);")->addBody("} elseif (\$xml instanceof \\DOMElement) {")->addBody("\treturn self::fromXmlElement(\$xml, \$group, \$id, \$object);")->addBody("} else {")->addBody("\tthrow new \\InvalidArgumentException('Expected XMLReader or DOMElement, got ' . gettype(\$xml) . (is_object(\$xml) ? ' of class ' . get_class(\$xml) : '') . '.');")->addBody("}"); $fromXmlReader = $class->addMethod("fromXmlReader"); $fromXmlReader->setStatic(true)->setVisibility("private"); $fromXmlReader->addParameter("xml")->setTypeHint("\\XMLReader"); $fromXmlReader->addParameter("group"); $fromXmlReader->addParameter("id"); $fromXmlReader->addParameter("object")->setTypeHint($type->getName()); $fromXmlReader->addBody("if (\$xml->nodeType !== \\XMLReader::ELEMENT) {")->addBody("\tthrow new \\InvalidArgumentException('Expects XMLReader to be positioned on ELEMENT node.');")->addBody("}")->addBody(""); $attributesByName = array(); foreach ($type->getProperties() as $property) { foreach ($property->getAnnotations("Skrz\\Meta\\XML\\XmlAttribute") as $xmlAttribute) { /** @var XmlAttribute $xmlAttribute */ $groupId = $groups[$xmlAttribute->group]; $name = strtolower($xmlAttribute->name); if (!isset($attributesByName[$name])) { $attributesByName[$name] = ""; } $attributesByName[$name] .= "if ((\$id & {$groupId}) > 0 && \$xml->namespaceURI === " . var_export($xmlAttribute->namespace, true) . ") {\n"; $attributesByName[$name] .= Strings::indent($this->assignObjectProperty($xmlAttribute, $property, "\$xml->value"), 1, "\t") . "\n"; $attributesByName[$name] .= "}\n"; } } if (!empty($attributesByName)) { $fromXmlReader->addBody("if (\$xml->moveToFirstAttribute()) {")->addBody("\tdo {")->addBody("\t\tswitch (strtolower(\$xml->localName)) {"); $i = 0; foreach ($attributesByName as $name => $code) { $fromXmlReader->addBody("\t\t\tcase " . var_export($name, true) . ":")->addBody(Strings::indent($code, 4, "\t"))->addBody("\t\t\t\tbreak;"); if ($i < count($attributesByName) - 1) { $fromXmlReader->addBody(""); } ++$i; } $fromXmlReader->addBody("\t\t}")->addBody("\t} while (\$xml->moveToNextAttribute());")->addBody("")->addBody("\t\$xml->moveToElement();")->addBody("}")->addBody(""); } $fromXmlReader->addBody("if ((\$id & {$valueGroupIdMask}) > 0) {"); $valueCount = 0; foreach ($type->getProperties() as $property) { foreach ($property->getAnnotations("Skrz\\Meta\\XML\\XmlValue") as $xmlValue) { /** @var XmlValue $xmlValue */ $groupId = $groups[$xmlValue->group]; $fromXmlReader->addBody("\tif ((\$id & {$groupId}) > 0) {")->addBody("\t\t\$value = self::xmlReadValue(\$xml);")->addBody(Strings::indent($this->assignObjectProperty($xmlValue, $property, "\$value"), 2, "\t"))->addBody("\t}")->addBody(""); ++$valueCount; } } if (!$valueCount) { $fromXmlReader->addBody("\t// @XmlValue not specified"); } $fromXmlReader->addBody("} else {"); $elementsByName = array(); $endElementsByName = array(); $wrappers = []; foreach ($type->getProperties() as $property) { foreach ($property->getAnnotations("Skrz\\Meta\\XML\\XmlElementWrapper") as $xmlElementWrapper) { /** @var XmlElementWrapper $xmlElementWrapper */ $groupId = $groups[$xmlElementWrapper->group]; $name = strtolower($xmlElementWrapper->name); $wrapperId = $xmlElementWrapper->group . ":" . $property->getName(); if (!isset($wrappers[$wrapperId])) { $wrappers[$wrapperId] = 1 << count($wrappers); } if (!isset($elementsByName[$name])) { $elementsByName[$name] = ""; } $elementsByName[$name] .= "if ((\$id & {$groupId}) > 0 && \$xml->namespaceURI === " . var_export($xmlElementWrapper->namespace, true) . " && \$depth === 2) {\n"; $elementsByName[$name] .= "\t\$wrapped |= {$wrappers[$wrapperId]};\n"; $elementsByName[$name] .= "}\n"; if (!isset($endElementsByName[$name])) { $endElementsByName[$name] = ""; } $endElementsByName[$name] .= "if ((\$id & {$groupId}) > 0 && \$xml->namespaceURI === " . var_export($xmlElementWrapper->namespace, true) . " && \$depth === 2) {\n"; $endElementsByName[$name] .= "\t\$wrapped &= ~{$wrappers[$wrapperId]};\n"; $endElementsByName[$name] .= "}\n"; } foreach ($property->getAnnotations("Skrz\\Meta\\XML\\XmlElement") as $xmlElement) { /** @var XmlElement $xmlElement */ $groupId = $groups[$xmlElement->group]; $name = strtolower($xmlElement->name); $wrapperId = $xmlElement->group . ":" . $property->getName(); if (!isset($elementsByName[$name])) { $elementsByName[$name] = ""; } $isArray = false; $propertyType = $property->getType(); if ($propertyType instanceof ArrayType) { $isArray = true; $propertyType = $propertyType->getBaseType(); } if ($propertyType instanceof ArrayType) { throw new MetaException("fromXml() cannot process multi-dimensional arrays ({$type->getName()}::\${$property->getName()})."); } $elementsByName[$name] .= "if ((\$id & {$groupId}) > 0 && \$xml->namespaceURI === " . var_export($xmlElement->namespace, true) . " && "; if (isset($wrappers[$wrapperId])) { $elementsByName[$name] .= "(\$depth === 2 || (\$depth === 3 && (\$wrapped & {$wrappers[$wrapperId]}) > 0))"; } else { $elementsByName[$name] .= "\$depth === 2"; } $elementsByName[$name] .= ") {\n"; if ($propertyType instanceof Type) { $propertyTypeMetaClassName = $spec->createMetaClassName($propertyType); $ns->addUse($propertyTypeMetaClassName, null, $propertyTypeMetaClassNameAlias); $elementsByName[$name] .= "\t\$value = {$propertyTypeMetaClassNameAlias}::fromXml(\$xml, \$group" . ($isArray ? "" : ", isset(\$object->{$property->getName()}) ? \$object->{$property->getName()} : null") . ");\n"; } else { $elementsByName[$name] .= "\t\$value = self::xmlReadValue(\$xml);\n"; } $elementsByName[$name] .= Strings::indent($this->assignObjectProperty($xmlElement, $property, "\$value", $isArray), 1, "\t") . "\n"; $elementsByName[$name] .= "}\n"; } } if (empty($elementsByName)) { $fromXmlReader->addBody("\t// @XmlElement not specified"); } else { $fromXmlReader->addBody("\t\$depth = intval(!\$xml->isEmptyElement);")->addBody("\t\$wrapped = 0;")->addBody("\twhile (\$depth > 0 && \$xml->read()) {")->addBody("\t\tif (\$xml->nodeType === \\XMLReader::ELEMENT) {")->addBody("\t\t\t++\$depth;")->addBody("\t\t\tswitch (strtolower(\$xml->localName)) {"); $i = 0; foreach ($elementsByName as $name => $code) { $fromXmlReader->addBody("\t\t\t\tcase " . var_export($name, true) . ":")->addBody(Strings::indent($code, 5, "\t"))->addBody("\t\t\t\t\tbreak;"); if ($i < count($elementsByName) - 1) { $fromXmlReader->addBody(""); } ++$i; } $fromXmlReader->addBody("\t\t\t}")->addBody("\t\t}")->addBody("")->addBody("\t\tif (\$xml->nodeType === \\XMLReader::END_ELEMENT || (\$xml->nodeType === \\XMLReader::ELEMENT && \$xml->isEmptyElement)) {"); if (!empty($endElementsByName)) { $fromXmlReader->addBody("\t\t\tswitch (strtolower(\$xml->localName)) {"); $i = 0; foreach ($endElementsByName as $name => $code) { $fromXmlReader->addBody("\t\t\t\tcase " . var_export($name, true) . ":")->addBody(Strings::indent($code, 5, "\t"))->addBody("\t\t\t\t\tbreak;"); if ($i < count($endElementsByName) - 1) { $fromXmlReader->addBody(""); } ++$i; } $fromXmlReader->addBody("\t\t\t}"); } $fromXmlReader->addBody("\t\t\t--\$depth;")->addBody("\t\t}")->addBody("\t}"); } $fromXmlReader->addBody("}")->addBody(""); $fromXmlReader->addBody("return \$object;"); $xmlReadValue = $class->addMethod("xmlReadValue"); $xmlReadValue->setStatic(true)->setVisibility("private"); $xmlReadValue->addParameter("xml")->setTypeHint("\\XMLReader"); $xmlReadValue->addBody("\$value = null;")->addBody("\$valueDepth = intval(!\$xml->isEmptyElement);")->addBody("while (\$valueDepth > 0 && \$xml->read()) {")->addBody("\tif (\$xml->nodeType === \\XMLReader::ELEMENT && !\$xml->isEmptyElement) {")->addBody("\t\t++\$valueDepth;")->addBody("\t} elseif (\$xml->nodeType === \\XMLReader::END_ELEMENT) {")->addBody("\t\t--\$valueDepth;")->addBody("\t} elseif (\$xml->nodeType === \\XMLReader::TEXT || \$xml->nodeType === \\XMLReader::CDATA) {")->addBody("\t\t\$value .= \$xml->value;")->addBody("\t}")->addBody("}")->addBody("return \$value;"); $fromXmlElement = $class->addMethod("fromXmlElement"); $fromXmlElement->setStatic(true)->setVisibility("private"); $fromXmlElement->addParameter("xml")->setTypeHint("\\DOMElement"); $fromXmlElement->addParameter("group"); $fromXmlElement->addParameter("id"); $fromXmlElement->addParameter("object")->setTypeHint($type->getName()); foreach ($type->getProperties() as $property) { foreach ($property->getAnnotations("Skrz\\Meta\\XML\\XmlAttribute") as $xmlAttribute) { /** @var XmlAttribute $xmlAttribute */ $groupId = $groups[$xmlAttribute->group]; if ($xmlAttribute->namespace) { $fromXmlElement->addBody("if ((\$id & {$groupId}) > 0 && " . "\$xml->hasAttributeNS(" . var_export($xmlAttribute->namespace, true) . ", " . var_export($xmlAttribute->name, true) . ")) {"); $expr = "\$xml->getAttributeNS(" . var_export($xmlAttribute->namespace, true) . ", " . var_export($xmlAttribute->name, true) . ")"; } else { $fromXmlElement->addBody("if ((\$id & {$groupId}) > 0 && " . "\$xml->hasAttribute(" . var_export($xmlAttribute->name, true) . ")) {"); $expr = "\$xml->getAttribute(" . var_export($xmlAttribute->name, true) . ")"; } $fromXmlElement->addBody(Strings::indent($this->assignObjectProperty($xmlAttribute, $property, $expr), 1, "\t"))->addBody("}")->addBody(""); } } $fromXmlElement->addBody("if ((\$id & {$valueGroupIdMask}) > 0) {"); $valueCount = 0; foreach ($type->getProperties() as $property) { foreach ($property->getAnnotations("Skrz\\Meta\\XML\\XmlValue") as $xmlValue) { /** @var XmlValue $xmlValue */ $groupId = $groups[$xmlValue->group]; $fromXmlElement->addBody("\tif ((\$id & {$groupId}) > 0) {")->addBody(Strings::indent($this->assignObjectProperty($xmlValue, $property, "\$xml->textContent"), 2, "\t"))->addBody("\t}")->addBody(""); ++$valueCount; } } if (!$valueCount) { $fromXmlElement->addBody("\t// @XmlValue not specified"); } $fromXmlElement->addBody("} elseif (\$xml->childNodes->length > 0) {"); $elementsByName = array(); $wrappers = array(); foreach ($type->getProperties() as $property) { foreach ($property->getAnnotations("Skrz\\Meta\\XML\\XmlElementWrapper") as $xmlElementWrapper) { /** @var XmlElementWrapper $xmlElementWrapper */ $groupId = $groups[$xmlElementWrapper->group]; $name = strtolower($xmlElementWrapper->name); $wrapperId = $xmlElementWrapper->group . ":" . $property->getName(); if (!isset($wrappers[$wrapperId])) { $wrappers[$wrapperId] = 1 << count($wrappers); } if (!isset($elementsByName[$name])) { $elementsByName[$name] = ""; } $elementsByName[$name] .= "if ((\$id & {$groupId}) > 0 && \$xml->namespaceURI === " . var_export(empty($xmlElementWrapper->namespace) ? null : $xmlElementWrapper->namespace, true) . " && \$sp === 0 && \$node->childNodes->length > 0) {\n"; $elementsByName[$name] .= "\t\$wrapped |= {$wrappers[$wrapperId]};\n"; $elementsByName[$name] .= "\t\$push = true;\n"; $elementsByName[$name] .= "}\n"; } foreach ($property->getAnnotations("Skrz\\Meta\\XML\\XmlElement") as $xmlElement) { /** @var XmlElement $xmlElement */ $groupId = $groups[$xmlElement->group]; $name = strtolower($xmlElement->name); $wrapperId = $xmlElement->group . ":" . $property->getName(); if (!isset($elementsByName[$name])) { $elementsByName[$name] = ""; } $isArray = false; $propertyType = $property->getType(); if ($propertyType instanceof ArrayType) { $isArray = true; $propertyType = $propertyType->getBaseType(); } if ($propertyType instanceof ArrayType) { throw new MetaException("fromXml() cannot process multi-dimensional arrays ({$type->getName()}::\${$property->getName()})."); } $elementsByName[$name] .= "if ((\$id & {$groupId}) > 0 && \$xml->namespaceURI === " . var_export(empty($xmlElement->namespace) ? null : $xmlElement->namespace, true) . " && "; if (isset($wrappers[$wrapperId])) { $elementsByName[$name] .= "(\$sp === 0 || (\$sp === 1 && (\$wrapped & {$wrappers[$wrapperId]}) > 0))"; } else { $elementsByName[$name] .= "\$sp === 0"; } $elementsByName[$name] .= ") {\n"; if ($propertyType instanceof Type) { $propertyTypeMetaClassName = $spec->createMetaClassName($propertyType); $ns->addUse($propertyTypeMetaClassName, null, $propertyTypeMetaClassNameAlias); $elementsByName[$name] .= "\t\$value = {$propertyTypeMetaClassNameAlias}::fromXml(\$node, \$group" . ($isArray ? "" : ", isset(\$object->{$property->getName()}) ? \$object->{$property->getName()} : null") . ");\n"; } else { $elementsByName[$name] .= "\t\$value = \$node->textContent;\n"; } $elementsByName[$name] .= Strings::indent($this->assignObjectProperty($xmlElement, $property, "\$value", $isArray), 1, "\t") . "\n"; $elementsByName[$name] .= "}\n"; } } if (empty($elementsByName)) { $fromXmlElement->addBody("\t// @XmlElement not specified"); } else { $fromXmlElement->addBody("\t\$stack = [[\$xml->childNodes, 0]];")->addBody("\t\$sp = 0;")->addBody("\t\$wrapped = 0;")->addBody("\t\$push = false;")->addBody("\twhile (!empty(\$stack)) {")->addBody("\t\t\$node = \$stack[\$sp][0]->item(\$stack[\$sp][1]);")->addBody("\t\tif (\$node->nodeType !== XML_ELEMENT_NODE) {")->addBody("\t\t\tcontinue;")->addBody("\t\t}")->addBody("")->addBody("\t\tswitch (strtolower(\$node->localName)) {"); $i = 0; foreach ($elementsByName as $name => $code) { $fromXmlElement->addBody("\t\t\tcase " . var_export($name, true) . ":")->addBody(Strings::indent($code, 4, "\t"))->addBody("\t\t\t\tbreak;"); if ($i < count($elementsByName) - 1) { $fromXmlElement->addBody(""); } ++$i; } $fromXmlElement->addBody("\t\t}")->addBody("\t\t++\$stack[\$sp][1];")->addBody("\t\tif (\$stack[\$sp][1] >= \$stack[\$sp][0]->length) {")->addBody("\t\t\tunset(\$stack[\$sp]);")->addBody("\t\t\t--\$sp;")->addBody("\t\t\t\$wrapped = 0;")->addBody("\t\t}")->addBody("\t\tif (\$push) {")->addBody("\t\t\t\$push = false;")->addBody("\t\t\t\$stack[++\$sp] = [\$node->childNodes, 0];")->addBody("\t\t}")->addBody("\t}"); } $fromXmlElement->addBody("}")->addBody(""); $fromXmlElement->addBody("return \$object;"); // toXml() $toXml = $class->addMethod("toXml"); $toXml->setStatic(true); $toXml->addParameter("object"); $toXml->addParameter("group")->setOptional(true); $toXml->addParameter("filterOrXml"); $toXml->addParameter("xml")->setOptional(true); $toXml->addParameter("el")->setOptional(true); $toXml->addComment("Serializes \\{$type->getName()} to XML")->addComment("")->addComment("@param {$typeAlias} \$object")->addComment("@param string \$group")->addComment("@param array|\\XMLWriter|\\DOMDocument \$filterOrXml")->addComment("@param \\XMLWriter|\\DOMDocument|\\DOMElement \$xml")->addComment("@param \\DOMElement \$el")->addComment("")->addComment("@throws \\InvalidArgumentException")->addComment("")->addComment("@return \\DOMElement|void"); $ns->addUse("Skrz\\Meta\\Stack", null, $stackAlias); $toXml->addBody("if (\$object === null) {")->addBody("\treturn null;")->addBody("}")->addBody("")->addBody("if (!isset(self::\$xmlGroups[\$group])) {")->addBody("\tthrow new \\InvalidArgumentException('Group \\'' . \$group . '\\' not supported for ' . " . var_export($type->getName(), true) . " . '.');")->addBody("} else {")->addBody("\t\$id = self::\$xmlGroups[\$group];")->addBody("}")->addBody("")->addBody("if (!(\$object instanceof {$typeAlias})) {")->addBody("\tthrow new \\InvalidArgumentException('You have to pass object of class {$type->getName()}.');")->addBody("}")->addBody("")->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("")->addBody("if (\$filterOrXml instanceof \\XMLWriter || \$filterOrXml instanceof \\DOMDocument) {")->addBody("\t\$filter = null;")->addBody("\t\$el = \$xml;")->addBody("\t\$xml = \$filterOrXml;")->addBody("} else {")->addBody("\t\$filter = \$filterOrXml;")->addBody("}")->addBody("")->addBody("try {")->addBody("\tif (\$xml instanceof \\XMLWriter) {")->addBody("\t\tself::toXmlWriter(\$object, \$group, \$id, \$filter, \$xml);")->addBody("\t\t\$ret = null;")->addBody("\t} elseif (\$xml instanceof \\DOMDocument) {")->addBody("\t\t\$ret = self::toXmlElement(\$object, \$group, \$id, \$filter, \$xml, \$el);")->addBody("\t} else {")->addBody("\t\tthrow new \\InvalidArgumentException('You have to supply either XMLWriter, or DOMDocument.');")->addBody("\t}")->addBody("} catch (\\Exception \$e) {")->addBody("\t{$stackAlias}::\$objects->detach(\$object);")->addBody("\tthrow \$e;")->addBody("}")->addBody("")->addBody("{$stackAlias}::\$objects->detach(\$object);")->addBody("")->addBody("return \$ret;"); $toXmlWriter = $class->addMethod("toXmlWriter"); $toXmlWriter->setStatic(true)->setVisibility("private"); $toXmlWriter->addParameter("object")->setTypeHint($type->getName()); $toXmlWriter->addParameter("group"); $toXmlWriter->addParameter("id"); $toXmlWriter->addParameter("filter"); $toXmlWriter->addParameter("xml")->setTypeHint("\\XMLWriter"); $toXmlWriter->addBody("if (count({$stackAlias}::\$objects) < 2) {"); foreach ($type->getAnnotations("Skrz\\Meta\\XML\\XmlElement") as $xmlElement) { /** @var XmlElement $xmlElement */ $groupId = $groups[$xmlElement->group]; $toXmlWriter->addBody("\tif ((\$id & {$groupId}) > 0) {"); if ($xmlElement->namespace) { $toXmlWriter->addBody("\t\t\$xml->startElementNS(null, " . var_export($xmlElement->name, true) . ", " . var_export($xmlElement->namespace, true) . ");"); } else { $toXmlWriter->addBody("\t\t\$xml->startElement(" . var_export($xmlElement->name, true) . ");"); } $toXmlWriter->addBody("\t}"); } $toXmlWriter->addBody("}")->addBody(""); foreach ($type->getProperties() as $property) { foreach ($property->getAnnotations("Skrz\\Meta\\XML\\XmlAttribute") as $xmlAttribute) { /** @var XmlAttribute $xmlAttribute */ $groupId = $groups[$xmlAttribute->group]; $toXmlWriter->addBody("if ((\$id & {$groupId}) > 0 && isset(\$object->{$property->getName()}) && (\$filter === null || isset(\$filter[" . var_export("@" . $xmlAttribute->name, true) . "]))) {"); $matchingPropertySerializer = null; foreach ($this->propertySerializers as $propertySerializer) { if ($propertySerializer->matchesSerialize($property, $xmlAttribute->getGroup())) { $matchingPropertySerializer = $propertySerializer; break; } } if ($matchingPropertySerializer) { $sevo = $matchingPropertySerializer->serialize($property, $xmlAttribute->group, "\$object->{$property->getName()}"); } elseif ($property->getType() instanceof ScalarType) { $sevo = StatementAndExpressionVO::withExpression("(string)\$object->{$property->getName()}"); } else { throw new MetaException("Unsupported property type " . get_class($property->getType()) . "."); } if ($sevo->getStatement()) { $toXmlWriter->addBody(Strings::indent($sevo->getStatement(), 2, "\t")); } if ($xmlAttribute->namespace) { $toXmlWriter->addBody("\t\$xml->writeAttributeNS(null, " . var_export($xmlAttribute->name, true) . ", " . var_export($xmlAttribute->namespace, true) . ", {$sevo->getExpression()});"); } else { $toXmlWriter->addBody("\t\$xml->writeAttribute(" . var_export($xmlAttribute->name, true) . ", {$sevo->getExpression()});"); } $toXmlWriter->addBody("}")->addBody(""); } } $toXmlWriter->addBody("if ((\$id & {$valueGroupIdMask}) > 0) {"); $valueCount = 0; foreach ($type->getProperties() as $property) { foreach ($property->getAnnotations("Skrz\\Meta\\XML\\XmlValue") as $xmlValue) { /** @var XmlValue $xmlValue */ $groupId = $groups[$xmlValue->group]; $toXmlWriter->addBody("\tif ((\$id & {$groupId}) > 0 && isset(\$object->{$property->getName()})) {"); $matchingPropertySerializer = null; foreach ($this->propertySerializers as $propertySerializer) { if ($propertySerializer->matchesSerialize($property, $xmlValue->getGroup())) { $matchingPropertySerializer = $propertySerializer; break; } } if ($matchingPropertySerializer) { $sevo = $matchingPropertySerializer->serialize($property, $xmlValue->group, "\$object->{$property->getName()}"); if ($sevo->getStatement()) { $toXmlWriter->addBody(Strings::indent($sevo->getStatement(), 2, "\t")); } $toXmlWriter->addBody("\t\t\$xml->text({$sevo->getExpression()});"); } elseif ($property->getType() instanceof ScalarType) { $toXmlWriter->addBody("\t\t\$xml->text((string)\$object->{$property->getName()});"); } else { throw new MetaException("Unsupported property type " . get_class($property->getType()) . "."); } $toXmlWriter->addBody("\t}")->addBody(""); ++$valueCount; } } if (!$valueCount) { $toXmlWriter->addBody("\t// @XmlValue not specified"); } $toXmlWriter->addBody("} else {"); $elementCount = 0; $wrappers = array(); foreach ($type->getProperties() as $property) { $wrappedGroups = array(); foreach ($property->getAnnotations("Skrz\\Meta\\XML\\XmlElementWrapper") as $xmlElementWrapper) { $nameKey = $xmlElementWrapper->namespace . ":" . $xmlElementWrapper->name; $wrappedGroups[$xmlElementWrapper->group] = true; if (!isset($wrappers[$nameKey])) { $wrappers[$nameKey] = []; } if (!isset($wrappers[$nameKey][$xmlElementWrapper->group])) { $wrappers[$nameKey][$xmlElementWrapper->group] = [$xmlElementWrapper, []]; } // select first XmlElementWrapper per group if (!isset($wrappers[$nameKey][1][$property->getName()])) { $wrappers[$nameKey][$xmlElementWrapper->group][1][$property->getName()] = $property; ++$elementCount; } } foreach ($property->getAnnotations("Skrz\\Meta\\XML\\XmlElement") as $xmlElement) { if (isset($wrappedGroups[$xmlElement->group])) { continue; } ++$elementCount; $groupId = $groups[$xmlElement->group]; $elementNamespaceStr = var_export($xmlElement->namespace, true); $elementNameStr = var_export($xmlElement->name, true); $toXmlWriter->addBody("\tif ((\$id & {$groupId}) > 0 && isset(\$object->{$property->getName()}) && (\$filter === null || isset(\$filter[{$elementNameStr}]))) {"); $matchingPropertySerializer = null; foreach ($this->propertySerializers as $propertySerializer) { if ($propertySerializer->matchesSerialize($property, $xmlElement->group)) { $matchingPropertySerializer = $propertySerializer; break; } } $baseType = $property->getType(); $isArray = false; if ($baseType instanceof ArrayType) { $isArray = true; $baseType = $baseType->getBaseType(); } if ($baseType instanceof ArrayType) { throw new MetaException("toXml() cannot process multi-dimensional arrays ({$type->getName()}::\${$property->getName()})."); } $indent = "\t\t"; $value = "\$object->{$property->getName()}"; if ($isArray) { $toXmlWriter->addBody("\t\tforeach ({$value} instanceof \\Traversable ? {$value} : (array){$value} as \$item) {"); $indent = "\t\t\t"; $value = "\$item"; } if ($xmlElement->namespace) { $toXmlWriter->addBody("{$indent}\$xml->startElementNS(null, {$elementNameStr}, {$elementNamespaceStr});"); } else { $toXmlWriter->addBody("{$indent}\$xml->startElement({$elementNameStr});"); } if ($matchingPropertySerializer) { $sevo = $matchingPropertySerializer->serialize($property, $xmlElement->group, $value); if ($sevo->getStatement()) { $toXmlWriter->addBody(Strings::indent($sevo->getStatement(), 2, "\t")); } $toXmlWriter->addBody("{$indent}\$xml->text({$sevo->getExpression()});"); } elseif ($baseType instanceof ScalarType) { $toXmlWriter->addBody("{$indent}\$xml->text((string){$value});"); } elseif ($baseType instanceof Type) { $propertyTypeMetaClassName = $spec->createMetaClassName($baseType); $ns->addUse($propertyTypeMetaClassName, null, $propertyTypeMetaClassNameAlias); $toXmlWriter->addBody("{$indent}{$propertyTypeMetaClassNameAlias}::toXml({$value}, " . "\$group, " . "\$filter === null ? null : \$filter[{$elementNameStr}], " . "\$xml" . ");"); } else { throw new MetaException("Unsupported property type " . get_class($property->getType()) . "."); } $toXmlWriter->addBody("{$indent}\$xml->endElement();"); if ($isArray) { $toXmlWriter->addBody("\t\t}"); } $toXmlWriter->addBody("\t}"); } } foreach ($wrappers as $wrapper) { foreach ($wrapper as $groupWrapper) { list($xmlElementWrapper, $properties) = $groupWrapper; /** @var Property[] $properties */ $groupId = $groups[$xmlElementWrapper->group]; $namespaceStr = var_export($xmlElementWrapper->namespace, true); $nameStr = var_export($xmlElementWrapper->name, true); $propertiesIssets = []; foreach ($properties as $property) { $propertiesIssets[] = "isset(\$object->{$property->getName()})"; } $toXmlWriter->addBody("\tif ((\$id & {$groupId}) > 0 && (" . implode(" || ", $propertiesIssets) . ") && (\$filter === null || isset(\$filter[{$nameStr}]))) {"); if ($xmlElementWrapper->namespace) { $toXmlWriter->addBody("\t\t\$xml->startElementNS(null, {$nameStr}, {$namespaceStr});"); } else { $toXmlWriter->addBody("\t\t\$xml->startElement({$nameStr});"); } foreach ($properties as $property) { foreach ($property->getAnnotations("Skrz\\Meta\\XML\\XmlElement") as $xmlElement) { if ($xmlElement->group !== $xmlElementWrapper->group) { // important! continue; } $elementNamespaceStr = var_export($xmlElement->namespace, true); $elementNameStr = var_export($xmlElement->name, true); // no need to check group ID, already checked by wrapper $toXmlWriter->addBody("\t\tif (isset(\$object->{$property->getName()}) && (\$filter === null || isset(\$filter[{$nameStr}][{$elementNameStr}]))) {"); $matchingPropertySerializer = null; foreach ($this->propertySerializers as $propertySerializer) { if ($propertySerializer->matchesSerialize($property, $xmlElement->group)) { $matchingPropertySerializer = $propertySerializer; break; } } $baseType = $property->getType(); $isArray = false; if ($baseType instanceof ArrayType) { $isArray = true; $baseType = $baseType->getBaseType(); } if ($baseType instanceof ArrayType) { throw new MetaException("toXml() cannot process multi-dimensional arrays ({$type->getName()}::\${$property->getName()})."); } $indent = "\t\t\t"; $value = "\$object->{$property->getName()}"; if ($isArray) { $toXmlWriter->addBody("\t\t\tforeach ({$value} instanceof \\Traversable ? {$value} : (array){$value} as \$item) {"); $indent = "\t\t\t\t"; $value = "\$item"; } if ($xmlElement->namespace) { $toXmlWriter->addBody("{$indent}\$xml->startElementNS(null, {$elementNameStr}, {$elementNamespaceStr});"); } else { $toXmlWriter->addBody("{$indent}\$xml->startElement({$elementNameStr});"); } if ($matchingPropertySerializer) { $sevo = $matchingPropertySerializer->serialize($property, $xmlElement->group, $value); if ($sevo->getStatement()) { $toXmlWriter->addBody(Strings::indent($sevo->getStatement(), 2, "\t")); } $toXmlWriter->addBody("{$indent}\$xml->text({$sevo->getExpression()});"); } elseif ($baseType instanceof ScalarType) { $toXmlWriter->addBody("{$indent}\$xml->text((string)\$object->{$property->getName()});"); } elseif ($baseType instanceof Type) { $propertyTypeMetaClassName = $spec->createMetaClassName($baseType); $ns->addUse($propertyTypeMetaClassName, null, $propertyTypeMetaClassNameAlias); $toXmlWriter->addBody("{$indent}{$propertyTypeMetaClassNameAlias}::toXml({$value}, " . "\$group, " . "\$filter === null ? null : \$filter[{$nameStr}][{$elementNameStr}], " . "\$xml" . ");"); } else { throw new MetaException("Unsupported property type " . get_class($property->getType()) . "."); } $toXmlWriter->addBody("{$indent}\$xml->endElement();"); if ($isArray) { $toXmlWriter->addBody("\t\t\t}"); } $toXmlWriter->addBody("\t\t}"); } } $toXmlWriter->addBody("\t\t\$xml->endElement();"); $toXmlWriter->addBody("\t}"); } } if (!$elementCount) { $toXmlWriter->addBody("\t// @XmlElement not specified"); } $toXmlWriter->addBody("}")->addBody(""); $toXmlWriter->addBody("if (count({$stackAlias}::\$objects) < 2) {")->addBody("\t\$xml->endElement();")->addBody("}"); $toXmlElement = $class->addMethod("toXmlElement"); $toXmlElement->setStatic(true)->setVisibility("private"); $toXmlElement->addParameter("object")->setTypeHint($type->getName()); $toXmlElement->addParameter("group"); $toXmlElement->addParameter("id"); $toXmlElement->addParameter("filter"); $toXmlElement->addParameter("xml")->setTypeHint("\\DOMDocument"); $toXmlElement->addParameter("el")->setTypeHint("\\DOMElement")->setOptional(true); foreach ($type->getAnnotations("Skrz\\Meta\\XML\\XmlElement") as $xmlElement) { /** @var XmlElement $xmlElement */ $groupId = $groups[$xmlElement->group]; $toXmlElement->addBody("if (!isset(\$el) && (\$id & {$groupId}) > 0) {"); if ($xmlElement->namespace) { $toXmlElement->addBody("\t\$el = \$xml->createElementNS(" . var_export($xmlElement->namespace, true) . ", " . var_export($xmlElement->name, true) . ");"); } else { $toXmlElement->addBody("\t\$el = \$xml->createElement(" . var_export($xmlElement->name, true) . ");"); } $toXmlElement->addBody("}")->addBody(""); } $toXmlElement->addBody("if (!isset(\$el)) {")->addBody("\tthrow new \\LogicException('Element has to exist by now.');")->addBody("}")->addBody(""); $attributeCount = 0; foreach ($type->getProperties() as $property) { foreach ($property->getAnnotations("Skrz\\Meta\\XML\\XmlAttribute") as $xmlAttribute) { ++$attributeCount; $groupId = $groups[$xmlAttribute->group]; $attributeNamespaceStr = var_export($xmlAttribute->namespace, true); $attributeNameStr = var_export($xmlAttribute->name, true); $attributeFilterStr = var_export("@" . $xmlAttribute->name, true); $toXmlElement->addBody("if ((\$id & {$groupId}) > 0 && isset(\$object->{$property->getName()}) && (\$filter === null || isset(\$filter[{$attributeFilterStr}]))) {"); $matchingPropertySerializer = null; foreach ($this->propertySerializers as $propertySerializer) { if ($propertySerializer->matchesSerialize($property, $xmlAttribute->getGroup())) { $matchingPropertySerializer = $propertySerializer; break; } } if ($matchingPropertySerializer) { $sevo = $matchingPropertySerializer->serialize($property, $xmlAttribute->group, "\$object->{$property->getName()}"); } elseif ($property->getType() instanceof ScalarType) { $sevo = StatementAndExpressionVO::withExpression("(string)\$object->{$property->getName()}"); } else { throw new MetaException("Unsupported property type " . get_class($property->getType()) . "."); } if ($sevo->getStatement()) { $toXmlElement->addBody(Strings::indent($sevo->getStatement(), 1, "\t")); } if ($xmlAttribute->namespace) { $toXmlElement->addBody("\t\$el->setAttributeNS({$attributeNamespaceStr}, {$attributeNameStr}, {$sevo->getExpression()});"); } else { $toXmlElement->addBody("\t\$el->setAttribute({$attributeNameStr}, {$sevo->getExpression()});"); } $toXmlElement->addBody("}"); } } if ($attributeCount) { $toXmlElement->addBody(""); } $toXmlElement->addBody("if ((\$id & {$valueGroupIdMask}) > 0) {"); $valueCount = 0; foreach ($type->getProperties() as $property) { foreach ($property->getAnnotations("Skrz\\Meta\\XML\\XmlValue") as $xmlValue) { /** @var XmlValue $xmlValue */ $groupId = $groups[$xmlValue->group]; $toXmlElement->addBody("\tif ((\$id & {$groupId}) > 0 && isset(\$object->{$property->getName()})) {"); $matchingPropertySerializer = null; foreach ($this->propertySerializers as $propertySerializer) { if ($propertySerializer->matchesSerialize($property, $xmlValue->getGroup())) { $matchingPropertySerializer = $propertySerializer; break; } } if ($matchingPropertySerializer) { $sevo = $matchingPropertySerializer->serialize($property, $xmlValue->group, "\$object->{$property->getName()}"); if ($sevo->getStatement()) { $toXmlElement->addBody(Strings::indent($sevo->getStatement(), 2, "\t")); } $toXmlElement->addBody("\t\t\$el->appendChild(new \\DOMText({$sevo->getExpression()}));"); } elseif ($property->getType() instanceof ScalarType) { $toXmlElement->addBody("\t\t\$el->appendChild(new \\DOMText((string)\$object->{$property->getName()}));"); } else { throw new MetaException("Unsupported property type " . get_class($property->getType()) . "."); } $toXmlElement->addBody("\t}"); ++$valueCount; } } if (!$valueCount) { $toXmlElement->addBody("\t// @XmlValue not specified"); } $toXmlElement->addBody("} else {"); $wrappers = array(); foreach ($type->getProperties() as $property) { $wrappedGroups = array(); foreach ($property->getAnnotations("Skrz\\Meta\\XML\\XmlElementWrapper") as $xmlElementWrapper) { $nameKey = $xmlElementWrapper->namespace . ":" . $xmlElementWrapper->name; $wrappedGroups[$xmlElementWrapper->group] = true; if (!isset($wrappers[$nameKey])) { $wrappers[$nameKey] = []; } if (!isset($wrappers[$nameKey][$xmlElementWrapper->group])) { $wrappers[$nameKey][$xmlElementWrapper->group] = [$xmlElementWrapper, []]; } // select first XmlElementWrapper per group if (!isset($wrappers[$nameKey][1][$property->getName()])) { $wrappers[$nameKey][$xmlElementWrapper->group][1][$property->getName()] = $property; ++$elementCount; } } foreach ($property->getAnnotations("Skrz\\Meta\\XML\\XmlElement") as $xmlElement) { if (isset($wrappedGroups[$xmlElement->group])) { continue; } ++$elementCount; $groupId = $groups[$xmlElement->group]; $elementNamespaceStr = var_export($xmlElement->namespace, true); $elementNameStr = var_export($xmlElement->name, true); $toXmlElement->addBody("\tif ((\$id & {$groupId}) > 0 && isset(\$object->{$property->getName()}) && (\$filter === null || isset(\$filter[{$elementNameStr}]))) {"); $matchingPropertySerializer = null; foreach ($this->propertySerializers as $propertySerializer) { if ($propertySerializer->matchesSerialize($property, $xmlElement->group)) { $matchingPropertySerializer = $propertySerializer; break; } } $baseType = $property->getType(); $isArray = false; if ($baseType instanceof ArrayType) { $isArray = true; $baseType = $baseType->getBaseType(); } if ($baseType instanceof ArrayType) { throw new MetaException("toXml() cannot process multi-dimensional arrays ({$type->getName()}::\${$property->getName()})."); } $indent = "\t\t"; $value = "\$object->{$property->getName()}"; if ($isArray) { $toXmlElement->addBody("\t\tforeach ({$value} instanceof \\Traversable ? {$value} : (array){$value} as \$item) {"); $indent = "\t\t\t"; $value = "\$item"; } if ($xmlElement->namespace) { $toXmlElement->addBody("{$indent}\$subEl = \$xml->createElementNS({$elementNamespaceStr}, {$elementNameStr});"); } else { $toXmlElement->addBody("{$indent}\$subEl = \$xml->createElement({$elementNameStr});"); } if ($matchingPropertySerializer) { $sevo = $matchingPropertySerializer->serialize($property, $xmlElement->group, $value); if ($sevo->getStatement()) { $toXmlElement->addBody(Strings::indent($sevo->getStatement(), 2, "\t")); } $toXmlElement->addBody("{$indent}\$subEl->appendChild(new \\DOMText({$sevo->getExpression()}));"); } elseif ($baseType instanceof ScalarType) { $toXmlElement->addBody("{$indent}\$subEl->appendChild(new \\DOMText((string){$value}));"); } elseif ($baseType instanceof Type) { $propertyTypeMetaClassName = $spec->createMetaClassName($baseType); $ns->addUse($propertyTypeMetaClassName, null, $propertyTypeMetaClassNameAlias); $toXmlElement->addBody("{$indent}{$propertyTypeMetaClassNameAlias}::toXml({$value}, " . "\$group, " . "\$filter === null ? null : \$filter[{$elementNameStr}], " . "\$xml, " . "\$subEl" . ");"); } else { throw new MetaException("Unsupported property type " . get_class($property->getType()) . "."); } $toXmlElement->addBody("{$indent}\$el->appendChild(\$subEl);"); if ($isArray) { $toXmlElement->addBody("\t\t}"); } $toXmlElement->addBody("\t}"); } } foreach ($wrappers as $wrapper) { foreach ($wrapper as $groupWrapper) { list($xmlElementWrapper, $properties) = $groupWrapper; /** @var Property[] $properties */ $groupId = $groups[$xmlElementWrapper->group]; $namespaceStr = var_export($xmlElementWrapper->namespace, true); $nameStr = var_export($xmlElementWrapper->name, true); $propertiesIssets = []; foreach ($properties as $property) { $propertiesIssets[] = "isset(\$object->{$property->getName()})"; } $toXmlElement->addBody("\tif ((\$id & {$groupId}) > 0 && (" . implode(" || ", $propertiesIssets) . ") && (\$filter === null || isset(\$filter[{$nameStr}]))) {"); if ($xmlElementWrapper->namespace) { $toXmlElement->addBody("\t\t\$wrapperEl = \$xml->createElementNS({$namespaceStr}, {$nameStr});"); } else { $toXmlElement->addBody("\t\t\$wrapperEl = \$xml->createElement({$nameStr});"); } foreach ($properties as $property) { foreach ($property->getAnnotations("Skrz\\Meta\\XML\\XmlElement") as $xmlElement) { if ($xmlElement->group !== $xmlElementWrapper->group) { // important! continue; } $elementNamespaceStr = var_export($xmlElement->namespace, true); $elementNameStr = var_export($xmlElement->name, true); // no need to check group ID, already checked by wrapper $toXmlElement->addBody("\t\tif (isset(\$object->{$property->getName()}) && (\$filter === null || isset(\$filter[{$nameStr}][{$elementNameStr}]))) {"); $matchingPropertySerializer = null; foreach ($this->propertySerializers as $propertySerializer) { if ($propertySerializer->matchesSerialize($property, $xmlElement->group)) { $matchingPropertySerializer = $propertySerializer; break; } } $baseType = $property->getType(); $isArray = false; if ($baseType instanceof ArrayType) { $isArray = true; $baseType = $baseType->getBaseType(); } if ($baseType instanceof ArrayType) { throw new MetaException("toXml() cannot process multi-dimensional arrays ({$type->getName()}::\${$property->getName()})."); } $indent = "\t\t\t"; $value = "\$object->{$property->getName()}"; if ($isArray) { $toXmlElement->addBody("\t\t\tforeach ({$value} instanceof \\Traversable ? {$value} : (array){$value} as \$item) {"); $indent = "\t\t\t\t"; $value = "\$item"; } if ($xmlElement->namespace) { $toXmlElement->addBody("{$indent}\$subEl = \$xml->createElementNS({$elementNamespaceStr}, {$elementNameStr});"); } else { $toXmlElement->addBody("{$indent}\$subEl = \$xml->createElement({$elementNameStr});"); } if ($matchingPropertySerializer) { $sevo = $matchingPropertySerializer->serialize($property, $xmlElement->group, $value); if ($sevo->getStatement()) { $toXmlElement->addBody(Strings::indent($sevo->getStatement(), 2, "\t")); } $toXmlElement->addBody("{$indent}\$subEl->appendChild(new \\DOMText({$sevo->getExpression()}));"); } elseif ($baseType instanceof ScalarType) { $toXmlElement->addBody("{$indent}\$subEl->appendChild(new \\DOMText((string)\$object->{$property->getName()}));"); } elseif ($baseType instanceof Type) { $propertyTypeMetaClassName = $spec->createMetaClassName($baseType); $ns->addUse($propertyTypeMetaClassName, null, $propertyTypeMetaClassNameAlias); $toXmlElement->addBody("{$indent}{$propertyTypeMetaClassNameAlias}::toXml({$value}, " . "\$group, " . "\$filter === null ? null : \$filter[{$nameStr}][{$elementNameStr}], " . "\$xml, " . "\$subEl" . ");"); } else { throw new MetaException("Unsupported property type " . get_class($property->getType()) . "."); } $toXmlElement->addBody("{$indent}\$wrapperEl->appendChild(\$subEl);"); if ($isArray) { $toXmlElement->addBody("\t\t\t}"); } $toXmlElement->addBody("\t\t}"); } } $toXmlElement->addBody("\t\t\$el->appendChild(\$wrapperEl);"); $toXmlElement->addBody("\t}"); } } if (!$elementCount) { $toXmlElement->addBody("\t// @XmlElement not specified"); } $toXmlElement->addBody("}")->addBody(""); $toXmlElement->addBody("return \$el;"); }
public function generateTests($outputFolder) { $container = \Testbench\ContainerFactory::create(FALSE); $presenterFactory = $container->getByType('Nette\\Application\\IPresenterFactory'); $presenters = $container->findByType('Nette\\Application\\UI\\Presenter'); foreach ($presenters as $presenter) { $this->renderMethods = $this->handleMethods = $this->componentMethods = []; /** @var \Nette\Application\UI\Presenter $service */ $service = $container->getService($presenter); if ($service instanceof \Testbench\Mocks\PresenterMock) { continue; } if ($service instanceof \KdybyModule\CliPresenter) { //Oh, Kdyby! :-( continue; } $rc = new \ReflectionClass($service); $renderPrefix = $service->formatActionMethod('') . '|' . $service->formatRenderMethod(''); $methods = $rc->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED); foreach ($methods as $method) { $methodName = $method->getName(); if (preg_match("~^({$renderPrefix})[a-z0-9]+~i", $methodName)) { try { $optionalArgs = $this->tryCall($service, $methodName, $service->getParameters(), TRUE); if (preg_match('~.*rss.*~i', $methodName)) { $this->renderMethods[$methodName] = 'rss'; } elseif (preg_match('~.*sitemap.*~i', $methodName)) { $this->renderMethods[$methodName] = 'sitemap'; } else { $requiredArgs = $this->tryCall($service, $methodName, $service->getParameters(), FALSE); $this->renderMethods[$methodName] = ['action', [$optionalArgs, $requiredArgs]]; } } catch (\Nette\Application\AbortException $exc) { $this->renderMethods[$methodName] = ['action', $this->getResponse($service)]; } catch (\Exception $exc) { $this->renderMethods[$methodName] = ['exception', $exc]; } } if (preg_match('~^handle[a-z0-9]+~i', $methodName)) { if ($methodName === 'handleInvalidLink') { //internal method continue; } $this->handleMethods[] = $methodName; } if (preg_match('~^createComponent[a-z0-9]+~i', $methodName)) { $method->setAccessible(TRUE); $form = $method->invoke($service); if ($form instanceof \Nette\Application\UI\Form) { $this->componentMethods[$methodName] = $form; } } } $testClassName = $rc->getShortName() . 'Test'; $testClass = new PhpGenerator\ClassType($testClassName); $testClass->setExtends('\\Tester\\TestCase'); $testClass->addTrait('\\Testbench\\TPresenter'); $testClass->addComment('@testCase'); foreach ($this->renderMethods as $testMethod => $testMethodType) { $generatedMethod = $testClass->addMethod('test' . ucfirst($testMethod)); $destination = $presenterFactory->unformatPresenterClass($rc->getName()) . ':'; $destination .= lcfirst(preg_replace('~^(action|render)([a-z]+)~i', '$2', $testMethod)); $extra = NULL; if (is_array($testMethodType)) { /** @var \Exception|\Nette\Application\IResponse $extra */ $extra = $testMethodType[1]; $testMethodType = $testMethodType[0]; //FIXME: fuj, hnus } switch ($testMethodType) { case 'rss': $generatedMethod->addBody('$this->checkRss(?);', [$destination]); break; case 'sitemap': $generatedMethod->addBody('$this->checkSitemap(?);', [$destination]); break; case 'action': if ($extra instanceof \Nette\Application\Responses\RedirectResponse) { $url = new \Nette\Http\Url($extra->getUrl()); $generatedMethod->addBody('$this->checkRedirect(?, ?);', [$destination, $url->getPath()]); } elseif ($extra instanceof \Nette\Application\Responses\JsonResponse) { $generatedMethod->addBody('$this->checkJson(?);', [$destination]); } else { if ($extra[0]) { $generatedMethod->addBody('//FIXME: parameters may not be correct'); $generatedMethod->addBody("\$this->checkAction(?, ?);\n", [$destination, $extra[0]]); $generatedMethod->addBody('$this->checkAction(?, ?);', [$destination, $extra[1]]); } else { $generatedMethod->addBody('$this->checkAction(?);', [$destination]); } } break; case 'exception': $this->generateExceptionBody($generatedMethod, $destination, $extra); break; } } foreach ($this->handleMethods as $testMethod) { $destination = $presenterFactory->unformatPresenterClass($rc->getName()); $action = lcfirst(preg_replace('~^handle([a-z]+)~i', '$1', $testMethod)); $testClass->addMethod('test' . ucfirst($testMethod))->addBody('$this->checkSignal(?, ?);', [$destination . ':', $action]); } foreach ($this->componentMethods as $testMethod => $form) { $destination = $presenterFactory->unformatPresenterClass($rc->getName()); $action = lcfirst(preg_replace('~^createComponent([a-z]+)~i', '$1', $testMethod)); $controls = ''; /** @var \Nette\Application\UI\Form $form */ foreach ($form->getControls() as $control) { if ($control->getName() === '_token_' || $control instanceof \Nette\Forms\Controls\SubmitButton) { continue; } $value = "'###', //FIXME: replace with value"; if ($control instanceof \Nette\Forms\Controls\Checkbox) { $value = 'FALSE'; } $controls .= "\t'" . $control->getName() . "' => {$value}\n"; } try { $form->onSuccess($form, $form->getValues()); $testClass->addMethod('test' . ucfirst($testMethod))->addBody("\$this->checkForm(?, ?, [\n" . $controls . '], ?);', [$destination . ':', $action, FALSE]); } catch (\Nette\Application\AbortException $exc) { $extra = $this->getResponse($service); $path = $extra ? (new \Nette\Http\Url($extra->getUrl()))->getPath() : '/'; $testClass->addMethod('test' . ucfirst($testMethod))->addBody("\$this->checkForm(?, ?, [\n" . $controls . '], ?);', [$destination . ':', $action, $path]); } catch (\Exception $exc) { //This sucks but we have to move on - failure is not an option } } $namespace = $rc->getNamespaceName(); $namespace = $namespace ? '\\' . $namespace : ''; $generatedTest = "<?php\n\nnamespace Tests{$namespace};\n\nuse Tester\\Assert;\n\n"; $depth = substr_count($namespace, '\\'); $levelsUp = str_repeat('../', $depth); $generatedTest .= "require __DIR__ . '/{$levelsUp}bootstrap.php';\n\n"; $generatedTest .= $testClass; $generatedTest .= "\n(new {$testClassName})->run();\n"; $testFileName = preg_replace('~\\\\~', DIRECTORY_SEPARATOR, get_class($service)); \Nette\Utils\FileSystem::write($outputFolder . '/' . $testFileName . '.phpt', $generatedTest); \Nette\Utils\FileSystem::createDir($outputFolder . '/_temp'); \Nette\Utils\FileSystem::write($outputFolder . '/tests.neon', <<<'NEON' # WARNING: it is CRITICAL that this file & directory are NOT accessible directly via a web browser! # http://nette.org/security-warning # (this is just an example) routing: routes: '/x/y[[[/<presenter>]/<action>][/<id>]]': 'Presenter:default' NEON ); } }
public function onGenerate(AbstractMetaSpec $spec, MetaSpecMatcher $matcher, Type $type, ClassType $class) { $namespace = $class->getNamespace(); // extend base class $namespace->addUse($type->getName(), null, $typeAlias); $class->addExtend($type->getName()); $class->addComment("Meta class for \\{$type->getName()}")->addComment("")->addComment("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")->addComment("!!! !!!")->addComment("!!! THIS CLASS HAS BEEN AUTO-GENERATED, DO NOT EDIT !!!")->addComment("!!! !!!")->addComment("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); // constructor $constructor = $class->addMethod("__construct"); $constructor->addComment("Constructor")->addBody("self::\$instance = \$this; // avoids cyclic dependency stack overflow"); if ($type->getConstructor()) { if ($type->getConstructor()->isPublic()) { $constructor->setVisibility("public"); } elseif ($type->getConstructor()->isProtected()) { $constructor->setVisibility("protected"); } elseif ($type->getConstructor()->isPrivate()) { $constructor->setVisibility("private"); } } else { $constructor->setVisibility("private"); } // implement base interface $namespace->addUse("Skrz\\Meta\\MetaInterface", null, $metaInterfaceAlias); $class->addImplement("Skrz\\Meta\\MetaInterface"); // getInstance() method $instance = $class->addProperty("instance"); $instance->setStatic(true); $instance->setVisibility("private"); $instance->addComment("@var {$class->getName()}"); $getInstance = $class->addMethod("getInstance"); $getInstance->setStatic(true); $getInstance->addComment("Returns instance of this meta class")->addComment("")->addComment("@return {$class->getName()}"); $getInstance->addBody("if (self::\$instance === null) {")->addBody("\tnew self(); // self::\$instance assigned in __construct")->addBody("}")->addBody("return self::\$instance;"); // create() method $create = $class->addMethod("create"); $create->setStatic(true); $create->addComment("Creates new instance of \\{$type->getName()}")->addComment("")->addComment("@throws \\InvalidArgumentException")->addComment("")->addComment("@return {$typeAlias}"); $create->addBody("switch (func_num_args()) {"); $maxArguments = 8; $constructMethod = $type->getConstructor(); for ($i = 0; $i <= $maxArguments; ++$i) { $create->addBody("\tcase {$i}:"); if ($constructMethod && $i < $constructMethod->getNumberOfRequiredParameters()) { $create->addBody("\t\tthrow new \\InvalidArgumentException('At least {$constructMethod->getNumberOfRequiredParameters()} arguments have to be supplied.');"); } else { $args = array(); for ($j = 0; $j < $i; ++$j) { $args[] = "func_get_arg({$j})"; } $create->addBody("\t\treturn new {$typeAlias}(" . implode(", ", $args) . ");"); } } $create->addBody("\tdefault:"); $create->addBody("\t\tthrow new \\InvalidArgumentException('More than {$maxArguments} arguments supplied, please be reasonable.');"); $create->addBody("}"); // reset() method $reset = $class->addMethod("reset"); $reset->setStatic(true); $reset->addComment("Resets properties of \\{$type->getName()} to default values\n")->addComment("")->addComment("@param {$typeAlias} \$object")->addComment("")->addComment("@throws \\InvalidArgumentException")->addComment("")->addComment("@return void"); $reset->addParameter("object"); $reset->addBody("if (!(\$object instanceof {$typeAlias})) {")->addBody("\tthrow new \\InvalidArgumentException('You have to pass object of class {$type->getName()}.');")->addBody("}"); foreach ($type->getProperties() as $property) { if ($property->hasAnnotation("Skrz\\Meta\\Transient")) { continue; } if ($property->isPrivate()) { throw new MetaException("Private property '{$type->getName()}::\${$property->getName()}'. " . "Either make the property protected/public if you need to process it, " . "or mark it using @Transient annotation."); } $reset->addBody("\$object->{$property->getName()} = " . var_export($property->getDefaultValue(), true) . ";"); } // hash() method $hash = $class->addMethod("hash"); $hash->setStatic(true); $hash->addComment("Computes hash of \\{$type->getName()}")->addComment("")->addComment("@param object \$object")->addComment("@param string|resource \$algoOrCtx")->addComment("@param bool \$raw")->addComment("")->addComment("@return string|void"); $hash->addParameter("object"); $hash->addParameter("algoOrCtx")->setDefaultValue("md5")->setOptional(true); $hash->addParameter("raw")->setDefaultValue(false)->setOptional(true); $hash->addBody("if (is_string(\$algoOrCtx)) {")->addBody("\t\$ctx = hash_init(\$algoOrCtx);")->addBody("} else {")->addBody("\t\$ctx = \$algoOrCtx;")->addBody("}")->addBody(""); foreach ($type->getProperties() as $property) { if ($property->hasAnnotation("Skrz\\Meta\\Transient")) { continue; } if ($property->hasAnnotation("Skrz\\Meta\\Hash")) { continue; } $objectPath = "\$object->{$property->getName()}"; $hash->addBody("if (isset({$objectPath})) {"); $hash->addBody("\thash_update(\$ctx, " . var_export($property->getName(), true) . ");"); $baseType = $property->getType(); $indent = "\t"; $before = ""; $after = ""; for ($i = 0; $baseType instanceof ArrayType; ++$i) { $arrayType = $baseType; $baseType = $arrayType->getBaseType(); $before .= "{$indent}foreach ({$objectPath} instanceof \\Traversable ? {$objectPath} : (array){$objectPath} as \$v{$i}) {\n"; $after = "{$indent}}\n" . $after; $indent .= "\t"; $objectPath = "\$v{$i}"; } if (!empty($before)) { $hash->addBody(rtrim($before)); } if ($baseType instanceof ScalarType) { $hash->addBody("{$indent}hash_update(\$ctx, (string){$objectPath});"); } elseif ($baseType instanceof Type) { $datetimeType = false; for ($t = $baseType; $t; $t = $t->getParentClass()) { if ($t->getName() === "DateTime") { $datetimeType = true; break; } } if ($datetimeType) { $hash->addBody("{$indent}hash_update(\$ctx, {$objectPath} instanceof \\DateTime ? {$objectPath}->format(\\DateTime::ISO8601) : '');"); } else { $propertyTypeMetaClassName = $spec->createMetaClassName($baseType); $namespace->addUse($propertyTypeMetaClassName, null, $propertyTypeMetaClassNameAlias); $hash->addBody("{$indent}{$propertyTypeMetaClassNameAlias}::hash({$objectPath}, \$ctx);"); } } else { throw new MetaException("Unsupported property type " . get_class($baseType) . " ({$type->getName()}::\${$property->getName()})."); } if (!empty($after)) { $hash->addBody(rtrim($after)); } $hash->addBody("}")->addBody(""); } $hash->addBody("if (is_string(\$algoOrCtx)) {")->addBody("\treturn hash_final(\$ctx, \$raw);")->addBody("} else {")->addBody("\treturn null;")->addBody("}"); }
/** * @param string $tableName * @param array $columns * @return void */ protected function generateTableGeneratedHyperRow($tableName, $columns) { $classFqn = $this->config['classes']['row']['generated']; $classFqn = Helpers::substituteClassWildcard($classFqn, $tableName); $className = Helpers::extractClassName($classFqn); $classNamespace = Helpers::extractNamespace($classFqn); $extendsFqn = $this->config['classes']['row']['base']; $extends = Helpers::formatClassName($extendsFqn, $classNamespace); $class = new ClassType($className); $class->setExtends($extends); // Add property annotations based on columns foreach ($columns as $column => $type) { if (in_array($type, [IStructure::FIELD_DATETIME, IStructure::FIELD_TIME, IStructure::FIELD_DATE, IStructure::FIELD_UNIX_TIMESTAMP])) { $type = '\\Nette\\Utils\\DateTime'; } $class->addComment("@property-read {$type} \${$column}"); } // Generate methods.row.getter foreach ((array) $this->config['methods']['row']['getter'] as $methodTemplate) { // Generate column getters foreach ($columns as $column => $type) { if (in_array($type, [IStructure::FIELD_DATETIME, IStructure::FIELD_TIME, IStructure::FIELD_DATE, IStructure::FIELD_UNIX_TIMESTAMP])) { $type = '\\Nette\\Utils\\DateTime'; } $methodName = Helpers::substituteMethodWildcard($methodTemplate, $column); $returnType = $type; $class->addMethod($methodName)->addBody('return $this->activeRow->?;', [$column])->addComment("@return {$returnType}"); // Add property annotation if (Strings::startsWith($methodName, 'get')) { $property = Strings::firstLower(Strings::substring($methodName, 3)); if ($property != $column) { $class->addComment("@property-read {$type} \${$property}"); } } } } // Generate methods.row.ref foreach ((array) $this->config['methods']['row']['ref'] as $methodTemplate) { // Generate 'ref' methods foreach ($this->structure->getBelongsToReference($tableName) as $referencingColumn => $referencedTable) { if (is_array($this->config['tables']) && !in_array($referencedTable, $this->config['tables'])) { continue; } $result = Helpers::underscoreToCamelWithoutPrefix(Strings::replace($referencingColumn, '~_id$~'), $tableName); $methodName = Helpers::substituteMethodWildcard($methodTemplate, $result); $returnType = $this->getTableClass('row', $referencedTable, $classNamespace); $class->addMethod($methodName)->addBody('return $this->ref(?, ?);', [$referencedTable, $referencingColumn])->addComment("@return {$returnType}"); // Add property annotations if (Strings::startsWith($methodName, 'get')) { $property = Strings::firstLower(Strings::substring($methodName, 3)); $class->addComment("@property-read {$returnType} \${$property}"); } } } // Generate methods.row.related foreach ((array) $this->config['methods']['row']['related'] as $methodTemplate) { // Generate 'related' methods foreach ($this->structure->getHasManyReference($tableName) as $relatedTable => $referencingColumns) { if (is_array($this->config['tables']) && !in_array($relatedTable, $this->config['tables'])) { continue; } foreach ($referencingColumns as $referencingColumn) { // Omit longest common prefix between $relatedTable and (this) $tableName $result = Helpers::underscoreToCamelWithoutPrefix($relatedTable, $tableName); if (count($referencingColumns) > 1) { $suffix = 'As' . Helpers::underscoreToCamel(Strings::replace($referencingColumn, '~_id$~')); } else { $suffix = NULL; } $methodName = Helpers::substituteMethodWildcard($methodTemplate, $result, $suffix); $returnType = $this->getTableClass('selection', $relatedTable, $classNamespace); $class->addMethod($methodName)->addBody('return $this->related(?, ?);', [$relatedTable, $referencingColumn])->addComment("@return {$returnType}"); // Add property annotations if (Strings::startsWith($methodName, 'get')) { $property = Strings::firstLower(Strings::substring($methodName, 3)); $class->addComment("@property-read {$returnType} \${$property}"); } } } } $code = implode("\n\n", ['<?php', "/**\n * This is a generated file. DO NOT EDIT. It will be overwritten.\n */", "namespace {$classNamespace};", $class]); $dir = $this->config['dir'] . '/' . 'tables' . '/' . $tableName; $file = $dir . '/' . $className . '.php'; $this->writeIfChanged($file, $code); }
/** * Generates body of service method. * @return string */ private function generateService($name) { $def = $this->builder->getDefinition($name); if ($def->isDynamic()) { return PhpHelpers::formatArgs('throw new Nette\\DI\\ServiceCreationException(?);', ["Unable to create dynamic service '{$name}', it must be added using addService()"]); } $entity = $def->getFactory()->getEntity(); $serviceRef = $this->builder->getServiceName($entity); $factory = $serviceRef && !$def->getFactory()->arguments && !$def->getSetup() && $def->getImplementMode() !== $def::IMPLEMENT_MODE_CREATE ? new Statement(['@' . ContainerBuilder::THIS_CONTAINER, 'getService'], [$serviceRef]) : $def->getFactory(); $this->currentService = NULL; $code = '$service = ' . $this->formatStatement($factory) . ";\n"; if (($class = $def->getClass()) && !$serviceRef && $class !== $entity && !(is_string($entity) && preg_match('#^[\\w\\\\]+\\z#', $entity) && is_subclass_of($entity, $class))) { $code .= PhpHelpers::formatArgs("if (!\$service instanceof {$class}) {\n" . "\tthrow new Nette\\UnexpectedValueException(?);\n}\n", ["Unable to create service '{$name}', value returned by factory is not {$class} type."]); } $this->currentService = $name; foreach ($def->getSetup() as $setup) { $code .= $this->formatStatement($setup) . ";\n"; } $code .= 'return $service;'; if (!$def->getImplement()) { return $code; } $factoryClass = new Nette\PhpGenerator\ClassType(); $factoryClass->setName('($this)')->addImplement($def->getImplement()); $factoryClass->addProperty('container')->setVisibility('private'); $factoryClass->addMethod('__construct')->addBody('$this->container = $container;')->addParameter('container')->setTypeHint($this->className); $factoryClass->addMethod($def->getImplementMode())->setParameters($this->convertParameters($def->parameters))->setBody(str_replace('$this', '$this->container', $code))->setReturnType(PHP_VERSION_ID >= 70000 ? $def->getClass() : NULL); if (PHP_VERSION_ID < 70000) { $this->generatedClasses[] = $factoryClass; $factoryClass->setName(str_replace(['\\', '.'], '_', "{$this->className}_{$def->getImplement()}Impl_{$name}")); return "return new {$factoryClass->getName()}(\$this);"; } return 'return new ' . rtrim($factoryClass) . ';'; }
public function onGenerate(AbstractMetaSpec $spec, MetaSpecMatcher $matcher, Type $type, ClassType $class) { $ns = $class->getNamespace(); $ns->addUse("Skrz\\Meta\\Protobuf\\ProtobufMetaInterface"); $ns->addUse($type->getName(), null, $typeAlias); $ns->addUse("Skrz\\Meta\\Protobuf\\Binary", null, $binary); $ns->addUse("Skrz\\Meta\\Protobuf\\ProtobufException", null, $protobufExceptionAlias); $class->addImplement("Skrz\\Meta\\Protobuf\\ProtobufMetaInterface"); foreach ($type->getProperties() as $property) { if ($property->hasAnnotation("Skrz\\Meta\\Transient")) { continue; } /** @var ProtobufField $field */ $field = $property->getAnnotation("Skrz\\Meta\\Protobuf\\ProtobufField"); $class->addConst(strtoupper(trim(preg_replace("/([A-Z])/", "_\$1", $property->getName() . "ProtobufField"), "_")), $field->number); } $fromProtobuf = $class->addMethod("fromProtobuf"); $fromProtobuf->setStatic(true); $fromProtobuf->addParameter("input"); $fromProtobuf->addParameter("object", null)->setOptional(true); $fromProtobuf->addParameter("start", 0)->setReference(true)->setOptional(true); $fromProtobuf->addParameter("end", null)->setOptional(true); $fromProtobuf->addComment("Creates \\{$type->getName()} object from serialized Protocol Buffers message.")->addComment("")->addComment("@param string \$input")->addComment("@param {$typeAlias} \$object")->addComment("@param int \$start")->addComment("@param int \$end")->addComment("")->addComment("@throws \\Exception")->addComment("")->addComment("@return {$typeAlias}"); $fromProtobuf->addBody("if (\$object === null) {")->addBody("\t\$object = new {$typeAlias}();")->addBody("}")->addBody("")->addBody("if (\$end === null) {")->addBody("\t\$end = strlen(\$input);")->addBody("}")->addBody(""); $fromProtobuf->addBody("while (\$start < \$end) {"); $fromProtobuf->addBody("\t\$tag = {$binary}::decodeVarint(\$input, \$start);"); $fromProtobuf->addBody("\t\$wireType = \$tag & 0x7;"); $fromProtobuf->addBody("\t\$number = \$tag >> 3;"); $fromProtobuf->addBody("\tswitch (\$number) {"); foreach ($type->getProperties() as $property) { if ($property->hasAnnotation("Skrz\\Meta\\Transient")) { continue; } $propertyType = $property->getType(); $baseType = $propertyType; if ($baseType instanceof ArrayType) { $baseType = $baseType->getBaseType(); } /** @var ProtobufField $field */ $field = $property->getAnnotation("Skrz\\Meta\\Protobuf\\ProtobufField"); $fromProtobuf->addBody("\t\tcase {$field->number}:"); if ($field->packed) { $binaryWireType = WireTypeEnum::toBinaryWireType(WireTypeEnum::STRING); } else { $binaryWireType = WireTypeEnum::toBinaryWireType($field->wireType); } $fromProtobuf->addBody("\t\t\tif (\$wireType !== {$binaryWireType}) {"); $fromProtobuf->addBody("\t\t\t\tthrow new {$protobufExceptionAlias}('Unexpected wire type ' . \$wireType . ', expected {$binaryWireType}.', \$number);"); $fromProtobuf->addBody("\t\t\t}"); $propertyLhs = "\$object->{$property->getName()}"; if ($propertyType->isArray()) { $fromProtobuf->addBody("\t\t\tif (!(isset({$propertyLhs}) && is_array({$propertyLhs}))) {"); $fromProtobuf->addBody("\t\t\t\t{$propertyLhs} = array();"); $fromProtobuf->addBody("\t\t\t}"); $propertyLhs .= "[]"; } if ($field->packed) { $fromProtobuf->addBody("\t\t\t\$packedLength = {$binary}::decodeVarint(\$input, \$start);")->addBody("\t\t\t\$expectedPacked = \$start + \$packedLength;")->addBody("\t\t\tif (\$expectedPacked > \$end) {")->addBody("\t\t\t\tthrow new {$protobufExceptionAlias}('Not enough data.');")->addBody("\t\t\t}")->addBody("\t\t\twhile (\$start < \$expectedPacked) {"); $indent = "\t\t\t\t"; } else { $indent = "\t\t\t"; } switch ($field->wireType) { case WireTypeEnum::VARINT: $fromProtobuf->addBody("{$indent}{$propertyLhs} = " . ($baseType instanceof BoolType ? "(bool)" : "") . "{$binary}::decodeVarint(\$input, \$start);"); break; case WireTypeEnum::ZIGZAG: $fromProtobuf->addBody("{$indent}{$propertyLhs} = {$binary}::decodeZigzag(\$input, \$start);"); break; case WireTypeEnum::FIXED64: $fromProtobuf->addBody("{$indent}\$expectedStart = \$start + 8;")->addBody("{$indent}if (\$expectedStart > \$end) {")->addBody("{$indent}\tthrow new {$protobufExceptionAlias}('Not enough data.');")->addBody("{$indent}}"); if ($baseType instanceof FloatType) { $fromProtobuf->addBody("{$indent}{$propertyLhs} = {$binary}::decodeDouble(\$input, \$start);"); } elseif ($baseType instanceof IntType && $field->unsigned) { $fromProtobuf->addBody("{$indent}{$propertyLhs} = {$binary}::decodeUint64(\$input, \$start);"); } elseif ($baseType instanceof IntType && !$field->unsigned) { $fromProtobuf->addBody("{$indent}{$propertyLhs} = {$binary}::decodeInt64(\$input, \$start);"); } else { throw new MetaException("Property {$type->getName()}::\${$property->getName()} has wire type '{$field->wireType}' and base type {$baseType}. " . "'{$field->wireType}' supports only float or int base types."); } $fromProtobuf->addBody("{$indent}if (\$start !== \$expectedStart) {")->addBody("{$indent}\tthrow new {$protobufExceptionAlias}('Unexpected start. Expected ' . \$expectedStart . ', got ' . \$start . '.', \$number);")->addBody("{$indent}}"); break; case WireTypeEnum::FIXED32: $fromProtobuf->addBody("{$indent}\$expectedStart = \$start + 4;")->addBody("{$indent}if (\$expectedStart > \$end) {")->addBody("{$indent}\tthrow new {$protobufExceptionAlias}('Not enough data.');")->addBody("{$indent}}"); if ($baseType instanceof FloatType) { $fromProtobuf->addBody("{$indent}{$propertyLhs} = {$binary}::decodeFloat(\$input, \$start);"); } elseif ($baseType instanceof IntType && $field->unsigned) { $fromProtobuf->addBody("{$indent}{$propertyLhs} = {$binary}::decodeUint32(\$input, \$start);"); } elseif ($baseType instanceof IntType && !$field->unsigned) { $fromProtobuf->addBody("{$indent}{$propertyLhs} = {$binary}::decodeInt32(\$input, \$start);"); } else { throw new MetaException("Property {$type->getName()}::\${$property->getName()} has wire type '{$field->wireType}' and base type {$baseType}. " . "'{$field->wireType}' supports only float or int base types."); } $fromProtobuf->addBody("{$indent}if (\$start !== \$expectedStart) {")->addBody("{$indent}\tthrow new {$protobufExceptionAlias}('Unexpected start. Expected ' . \$expectedStart . ', got ' . \$start . '.', \$number);")->addBody("{$indent}}"); break; case WireTypeEnum::STRING: $fromProtobuf->addBody("{$indent}\$length = {$binary}::decodeVarint(\$input, \$start);")->addBody("{$indent}\$expectedStart = \$start + \$length;")->addBody("{$indent}if (\$expectedStart > \$end) {")->addBody("{$indent}\tthrow new {$protobufExceptionAlias}('Not enough data.');")->addBody("{$indent}}"); if ($baseType instanceof StringType) { $fromProtobuf->addBody("{$indent}{$propertyLhs} = substr(\$input, \$start, \$length);"); $fromProtobuf->addBody("{$indent}\$start += \$length;"); } elseif ($baseType instanceof Type) { $propertyTypeMetaClassName = $spec->createMetaClassName($baseType); $ns->addUse($propertyTypeMetaClassName, null, $propertyTypeMetaClassNameAlias); $fromProtobuf->addBody("{$indent}{$propertyLhs} = {$propertyTypeMetaClassNameAlias}::fromProtobuf(\$input, null, \$start, \$start + \$length);"); } else { throw new MetaException("Property {$type->getName()}::\${$property->getName()} has wire type '{$field->wireType}' and base type {$baseType}. " . "'{$field->wireType}' supports only string or object types."); } $fromProtobuf->addBody("{$indent}if (\$start !== \$expectedStart) {")->addBody("{$indent}\tthrow new {$protobufExceptionAlias}('Unexpected start. Expected ' . \$expectedStart . ', got ' . \$start . '.', \$number);")->addBody("{$indent}}"); break; default: throw new \InvalidArgumentException("Unhandled field wire type '{$field->wireType}'."); } if ($field->packed) { $fromProtobuf->addBody("\t\t\t}")->addBody("\t\t\tif (\$start !== \$expectedPacked) {")->addBody("\t\t\t\tthrow new {$protobufExceptionAlias}('Unexpected start. Expected ' . \$expectedPacked . ', got ' . \$start . '.', \$number);")->addBody("\t\t\t}"); } $fromProtobuf->addBody("\t\t\tbreak;"); } $fromProtobuf->addBody("\t\tdefault:")->addBody("\t\t\tswitch (\$wireType) {")->addBody("\t\t\t\tcase " . WireTypeEnum::toBinaryWireType(WireTypeEnum::VARINT) . ":")->addBody("\t\t\t\t\t{$binary}::decodeVarint(\$input, \$start);")->addBody("\t\t\t\t\tbreak;")->addBody("\t\t\t\tcase " . WireTypeEnum::toBinaryWireType(WireTypeEnum::FIXED64) . ":")->addBody("\t\t\t\t\t\$start += 8;")->addBody("\t\t\t\t\tbreak;")->addBody("\t\t\t\tcase " . WireTypeEnum::toBinaryWireType(WireTypeEnum::STRING) . ":")->addBody("\t\t\t\t\t\$start += {$binary}::decodeVarint(\$input, \$start);")->addBody("\t\t\t\t\tbreak;")->addBody("\t\t\t\tcase " . WireTypeEnum::toBinaryWireType(WireTypeEnum::FIXED32) . ":")->addBody("\t\t\t\t\t\$start += 4;")->addBody("\t\t\t\t\tbreak;")->addBody("\t\t\t\tdefault:")->addBody("\t\t\t\t\tthrow new {$protobufExceptionAlias}('Unexpected wire type ' . \$wireType . '.', \$number);")->addBody("\t\t\t}"); $fromProtobuf->addBody("\t}"); $fromProtobuf->addBody("}"); $fromProtobuf->addBody("")->addBody("return \$object;"); $toProtobuf = $class->addMethod("toProtobuf"); $toProtobuf->setStatic(true); $toProtobuf->addParameter("object"); $toProtobuf->addParameter("filter", null); $toProtobuf->addComment("Serialized \\{$type->getName()} to Protocol Buffers message.")->addComment("")->addComment("@param {$typeAlias} \$object")->addComment("@param array \$filter")->addComment("")->addComment("@throws \\Exception")->addComment("")->addComment("@return string"); $toProtobuf->addBody("\$output = '';")->addBody(""); foreach ($type->getProperties() as $property) { if ($property->hasAnnotation("Skrz\\Meta\\Transient")) { continue; } $propertyType = $property->getType(); $baseType = $propertyType; if ($baseType instanceof ArrayType) { $baseType = $baseType->getBaseType(); } /** @var ProtobufField $field */ $field = $property->getAnnotation("Skrz\\Meta\\Protobuf\\ProtobufField"); $toProtobuf->addBody("if (isset(\$object->{$property->getName()}) && (\$filter === null || isset(\$filter[" . var_export($property->getName(), true) . "]))) {"); $var = "\$object->{$property->getName()}"; $output = "\$output"; $indent = "\t"; if ($field->packed) { $toProtobuf->addBody("\t\$packedBuffer = '';")->addBody("\t\$output .= \"" . implode("", array_map(function ($s) { return "\\x" . $s; }, str_split(bin2hex(Binary::encodeVarint($field->number << 3 | WireTypeEnum::toBinaryWireType(WireTypeEnum::STRING))), 2))) . "\";"); $output = "\$packedBuffer"; } if ($propertyType->isArray()) { $toProtobuf->addBody("\tforeach ({$var} instanceof \\Traversable ? {$var} : (array){$var} as \$k => \$v) {"); $var = "\$v"; $indent .= "\t"; } if (!$field->packed) { $toProtobuf->addBody("{$indent}{$output} .= \"" . implode("", array_map(function ($s) { return "\\x" . $s; }, str_split(bin2hex(Binary::encodeVarint($field->number << 3 | WireTypeEnum::toBinaryWireType($field->wireType))), 2))) . "\";"); } switch ($field->wireType) { case WireTypeEnum::VARINT: $toProtobuf->addBody("{$indent}{$output} .= {$binary}::encodeVarint(" . ($baseType instanceof BoolType ? "(int)" : "") . "{$var});"); break; case WireTypeEnum::ZIGZAG: $toProtobuf->addBody("{$indent}{$output} .= {$binary}::encodeZigzag({$var});"); break; case WireTypeEnum::FIXED64: if ($baseType instanceof FloatType) { $toProtobuf->addBody("{$indent}{$output} .= {$binary}::encodeDouble({$var});"); } elseif ($baseType instanceof IntType && $field->unsigned) { $toProtobuf->addBody("{$indent}{$output} .= {$binary}::encodeUint64({$var});"); } elseif ($baseType instanceof IntType && !$field->unsigned) { $toProtobuf->addBody("{$indent}{$output} .= {$binary}::encodeInt64({$var});"); } else { throw new MetaException("Property {$type->getName()}::\${$property->getName()} has wire type '{$field->wireType}' and base type {$baseType}. " . "'{$field->wireType}' supports only float or int base types."); } break; case WireTypeEnum::STRING: if ($baseType instanceof StringType) { $toProtobuf->addBody("{$indent}{$output} .= {$binary}::encodeVarint(strlen({$var}));"); $toProtobuf->addBody("{$indent}{$output} .= {$var};"); } elseif ($baseType instanceof Type) { $propertyTypeMetaClassName = $spec->createMetaClassName($baseType); $ns->addUse($propertyTypeMetaClassName, null, $propertyTypeMetaClassNameAlias); $toProtobuf->addBody("{$indent}\$buffer = {$propertyTypeMetaClassNameAlias}::toProtobuf({$var}, \$filter === null ? null : \$filter[" . var_export($property->getName(), true) . "]);"); $toProtobuf->addBody("{$indent}{$output} .= {$binary}::encodeVarint(strlen(\$buffer));"); $toProtobuf->addBody("{$indent}{$output} .= \$buffer;"); } else { throw new MetaException("Property {$type->getName()}::\${$property->getName()} has wire type '{$field->wireType}' and base type {$baseType}. " . "'{$field->wireType}' supports only string or object types."); } break; case WireTypeEnum::FIXED32: if ($baseType instanceof FloatType) { $toProtobuf->addBody("{$indent}{$output} .= {$binary}::encodeFloat({$var});"); } elseif ($baseType instanceof IntType && $field->unsigned) { $toProtobuf->addBody("{$indent}{$output} .= {$binary}::encodeUint32({$var});"); } elseif ($baseType instanceof IntType && !$field->unsigned) { $toProtobuf->addBody("{$indent}{$output} .= {$binary}::encodeInt32({$var});"); } else { throw new MetaException("Property {$type->getName()}::\${$property->getName()} has wire type '{$field->wireType}' and base type {$baseType}. " . "'{$field->wireType}' supports only float or int base types."); } break; } if ($propertyType->isArray()) { $toProtobuf->addBody("\t}"); } if ($field->packed) { $toProtobuf->addBody("\t\$output .= {$binary}::encodeVarint(strlen(\$packedBuffer));"); $toProtobuf->addBody("\t\$output .= \$packedBuffer;"); } $toProtobuf->addBody("}")->addBody(""); } $toProtobuf->addBody("return \$output;"); }
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;"); } }
public function onGenerate(AbstractMetaSpec $spec, MetaSpecMatcher $matcher, Type $type, ClassType $class) { $ns = $class->getNamespace(); $inputOutputClasses = array($type->getName() => true); foreach ($type->getAnnotations("Skrz\\Meta\\JSON\\JsonDiscriminatorMap") as $discriminatorMap) { /** @var JsonDiscriminatorMap $discriminatorMap */ foreach ($discriminatorMap->map as $value => $className) { $inputOutputClasses[$className] = true; } } $inputOutputClasses = array_keys($inputOutputClasses); sort($inputOutputClasses); $inputOutputTypeHint = array(); foreach ($inputOutputClasses as $className) { $ns->addUse($className, null, $alias); $inputOutputTypeHint[] = $alias; } $inputOutputTypeHint = implode("|", $inputOutputTypeHint); $ns->addUse("Skrz\\Meta\\JSON\\JsonMetaInterface"); $ns->addUse($type->getName(), null, $typeAlias); $class->addImplement("Skrz\\Meta\\JSON\\JsonMetaInterface"); // fromJson() $fromJson = $class->addMethod("fromJson"); $fromJson->setStatic(true); $fromJson->addParameter("json"); $fromJson->addParameter("group")->setOptional(true); $fromJson->addParameter("object")->setOptional(true); $fromJson->addComment("Creates \\{$type->getName()} from JSON array / JSON serialized string")->addComment("")->addComment("@param array|string \$json")->addComment("@param string \$group")->addComment("@param {$inputOutputTypeHint} \$object")->addComment("")->addComment("@throws \\InvalidArgumentException")->addComment("")->addComment("@return {$inputOutputTypeHint}"); $fromJson->addBody("if (is_array(\$json)) {")->addBody("\t// ok, nothing to do here")->addBody("} elseif (is_string(\$json)) {")->addBody("\t\$decoded = json_decode(\$json, true);")->addBody("\tif (\$decoded === null && \$json !== '' && strcasecmp(\$json, 'null')) {")->addBody("\t\tthrow new \\InvalidArgumentException('Could not decode given JSON: ' . \$json . '.');")->addBody("\t}")->addBody("\t\$json = \$decoded;")->addBody("} else {")->addBody("\tthrow new \\InvalidArgumentException('Expected array, or string, ' . gettype(\$json) . ' given.');")->addBody("}")->addBody(""); $fromJson->addBody("return self::fromObject(\$json, 'json:' . \$group, \$object);"); // toJson() $toJson = $class->addMethod("toJson"); $toJson->setStatic(true); $toJson->addParameter("object"); $toJson->addParameter("group")->setOptional(true); $toJson->addParameter("filterOrOptions")->setOptional(true); $toJson->addParameter("options", 0)->setOptional(true); $toJson->addComment("Serializes \\{$type->getName()} to JSON string")->addComment("")->addComment("@param {$inputOutputTypeHint} \$object")->addComment("@param string \$group")->addComment("@param array|int \$filterOrOptions")->addComment("@param int \$options")->addComment("")->addComment("@throws \\InvalidArgumentException")->addComment("")->addComment("@return string"); $toJson->addBody("if (is_int(\$filterOrOptions)) {")->addBody("\t\$options = \$filterOrOptions;")->addBody("\t\$filterOrOptions = null;")->addBody("}")->addBody("")->addBody("return json_encode(self::toObject(\$object, 'json:' . \$group, \$filterOrOptions), \$options);"); // toJsonString() $toJsonString = $class->addMethod("toJsonString"); $toJsonString->setStatic(true); $toJsonString->addParameter("object"); $toJsonString->addParameter("group")->setOptional(true); $toJsonString->addComment("Serializes \\{$type->getName()} to JSON string (only for BC, TO BE REMOVED)")->addComment("")->addComment("@param {$inputOutputTypeHint} \$object")->addComment("@param string \$group")->addComment("")->addComment("@throws \\InvalidArgumentException")->addComment("")->addComment("@deprecated")->addComment("")->addComment("@return string"); $toJsonString->addBody("return self::toJson(\$object, \$group);"); // toJsonStringPretty() $toJsonStringPretty = $class->addMethod("toJsonStringPretty"); $toJsonStringPretty->setStatic(true); $toJsonStringPretty->addParameter("object"); $toJsonStringPretty->addParameter("group")->setOptional(true); $toJsonStringPretty->addComment("Serializes \\{$type->getName()} to JSON pretty string (only for BC, TO BE REMOVED)")->addComment("")->addComment("@param {$inputOutputTypeHint} \$object")->addComment("@param string \$group")->addComment("")->addComment("@throws \\InvalidArgumentException")->addComment("")->addComment("@deprecated")->addComment("")->addComment("@return string"); $toJsonStringPretty->addBody("return self::toJson(\$object, \$group, JSON_PRETTY_PRINT);"); // fromArrayOfJson(), toArrayOfJson() $fromArrayOfJson = $class->addMethod("fromArrayOfJson"); $fromArrayOfJson->setStatic(true); $fromArrayOfJson->addParameter("input"); $fromArrayOfJson->addParameter("group")->setOptional(true); $fromArrayOfJson->addParameter("object")->setOptional(true); $fromArrayOfJson->addComment("Creates \\{$type->getName()} from array of JSON-serialized properties")->addComment("")->addComment("@param array \$input")->addComment("@param string \$group")->addComment("@param {$inputOutputTypeHint} \$object")->addComment("")->addComment("@return {$inputOutputTypeHint}"); $fromArrayOfJson->addBody("\$group = 'json:' . \$group;")->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(""); $toArrayOfJson = $class->addMethod("toArrayOfJson"); $toArrayOfJson->setStatic(true); $toArrayOfJson->addParameter("object"); $toArrayOfJson->addParameter("group")->setOptional(true); $toArrayOfJson->addParameter("filterOrOptions", 0)->setOptional(true); $toArrayOfJson->addParameter("options", 0)->setOptional(true); $toArrayOfJson->addComment("Transforms \\{$type->getName()} into array of JSON-serialized strings")->addComment("")->addComment("@param {$inputOutputTypeHint} \$object")->addComment("@param string \$group")->addComment("@param array|int \$filterOrOptions")->addComment("@param int \$options")->addComment("")->addComment("@throws \\InvalidArgumentException")->addComment("")->addComment("@return array"); $toArrayOfJson->addBody("if (is_int(\$filterOrOptions)) {")->addBody("\t\$options = \$filterOrOptions;")->addBody("\t\$filter = null;")->addBody("} else {")->addBody("\t\$filter = \$filterOrOptions;")->addBody("}")->addBody("")->addBody("\$group = 'json:' . \$group;")->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("")->addBody("\$output = (array)self::toObject(\$object, \$group, \$filter);")->addBody(""); $groups = $class->getProperty("groups")->value; foreach ($type->getProperties() as $property) { if ($property->getType() instanceof ScalarType) { continue; // skip scalar fields } foreach ($property->getAnnotations("Skrz\\Meta\\JSON\\JsonProperty") as $jsonProperty) { /** @var JsonProperty $jsonProperty */ $arrayOffset = new PhpArrayOffset(); $arrayOffset->offset = $jsonProperty->name; $arrayOffset->group = "json:" . $jsonProperty->group; if ($this->phpModule->getMatchingPropertySerializer($property, $arrayOffset) !== null) { continue; // skip custom-serialized fields } $groupId = $groups[$arrayOffset->group]; $inputPath = var_export($arrayOffset->offset, true); $fromArrayOfJson->addBody("if ((\$id & {$groupId}) > 0 && isset(\$input[{$inputPath}]) && is_string(\$input[{$inputPath}])) {")->addBody("\t\$decoded = json_decode(\$input[{$inputPath}], true);")->addBody("\tif (\$decoded === null && \$input[{$inputPath}] !== '' && strcasecmp(\$input[{$inputPath}], 'null')) {")->addBody("\t\tthrow new \\InvalidArgumentException('Could not decode given JSON: ' . \$input[{$inputPath}] . '.');")->addBody("\t}")->addBody("\t\$input[{$inputPath}] = \$decoded;")->addBody("}")->addBody(""); $toArrayOfJson->addBody("if ((\$id & {$groupId}) > 0 && isset(\$output[{$inputPath}]) && (\$filter === null || isset(\$filter[{$inputPath}]))) {")->addBody("\t\$output[{$inputPath}] = json_encode(\$output[{$inputPath}], \$options);")->addBody("}")->addBody(""); } } $fromArrayOfJson->addBody("/** @var object \$input */")->addBody("return self::fromObject(\$input, \$group, \$object);"); $toArrayOfJson->addBody("return \$output;"); }