addProperty() public method

public addProperty ( $name, $value = NULL ) : Property
return Property
 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));
 }
Beispiel #2
0
 /**
  * [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);
     }
 }
Beispiel #3
0
 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;");
 }
Beispiel #4
0
 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("}");
 }
Beispiel #5
0
 /**
  * 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) . ';';
 }
Beispiel #6
0
 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;");
     }
 }