diff --git a/fhir_test_resources.tar.gz b/fhir_test_resources.tar.gz deleted file mode 100644 index 6d0fede5..00000000 Binary files a/fhir_test_resources.tar.gz and /dev/null differ diff --git a/files/constants.php b/files/constants.php index ad33077b..68b3ba10 100644 --- a/files/constants.php +++ b/files/constants.php @@ -111,6 +111,7 @@ const PHPFHIR_TYPES_INTERFACE_VALUE_CONTAINER_TYPE = 'ValueContainerTypeInterface'; const PHPFHIR_TYPES_INTERFACE_RESOURCE_TYPE = 'ResourceTypeInterface'; const PHPFHIR_TYPES_INTERFACE_CONTAINED_TYPE = 'ContainedTypeInterface'; +const PHPFHIR_TYPES_INTERFACE_RESOURCE_CONTAINER_TYPE = 'ResourceContainerTypeInterface'; const PHPFHIR_TYPES_INTERFACE_COMMENT_CONTAINER = 'CommentContainerInterface'; const PHPFHIR_TYPES_TRAIT_COMMENT_CONTAINER = 'CommentContainerTrait'; const PHPFHIR_TYPES_TRAIT_VALUE_CONTAINER = 'ValueContainerTrait'; @@ -150,8 +151,8 @@ // Validation entities const PHPFHIR_VALIDATION_CLASSNAME_VALIDATOR = 'Validator'; +const PHPFHIR_VALIDATION_INTERFACE_RULE = 'RuleInterface'; const PHPFHIR_VALIDATION_TRAIT_TYPE_VALIDATIONS = 'TypeValidationsTrait'; -const PHPFHIR_VALIDATION_INTERFACE_VALIDATION_RULE = 'ValidationRuleInterface'; // Base validation rules const PHPFHIR_VALIDATION_RULE_CLASSNAME_VALUE_ONE_OF = 'ValueOneOfRule'; @@ -173,8 +174,11 @@ const PHPFHIR_TEST_CLIENT_CLASSNAME_CONFIG = PHPFHIR_CLIENT_CLASSNAME_CONFIG . 'Test'; const PHPFHIR_TEST_CLIENT_CLASSNAME_CLIENT = PHPFHIR_CLIENT_CLASSNAME_CLIENT . 'Test'; -const PHPFHIR_TEST_CLASSNAME_MOCK_RESOURCE_TYPE = 'MockResourceType'; +const PHPFHIR_TEST_TRAIT_MOCK_TYPE_FIELDS = 'MockTypeFieldsTrait'; const PHPFHIR_TEST_CLASSNAME_MOCK_STRING_PRIMITIVE_TYPE = 'MockStringPrimitiveType'; +const PHPFHIR_TEST_CLASSNAME_MOCK_ELEMENT_TYPE = 'MockElementType'; +const PHPFHIR_TEST_CLASSNAME_MOCK_PRIMITIVE_CONTAINER_TPYE = 'MockPrimitiveContainerType'; +const PHPFHIR_TEST_CLASSNAME_MOCK_RESOURCE_TYPE = 'MockResourceType'; // Test constant names const PHPFHIR_TEST_CONSTANT_SERVER_ADDR = 'PHPFHIR_TEST_SERVER_ADDR'; diff --git a/src/Utilities/ImportUtils.php b/src/Utilities/ImportUtils.php index 310c15e7..9c4bfcc8 100644 --- a/src/Utilities/ImportUtils.php +++ b/src/Utilities/ImportUtils.php @@ -43,7 +43,7 @@ public static function buildPropertyValidationRuleImports(Type $type, Property $ { $imports = $type->getImports(); - foreach($property->buildValidationMap($type) as $ruleClass => $_) { + foreach ($property->buildValidationMap($type) as $ruleClass => $_) { $imports->addCoreFileImportsByName($ruleClass); } } @@ -135,8 +135,8 @@ public static function buildVersionTypeImports(Version $version, Type $type): vo // immediately add self $imports->addImport($type->getFullyQualifiedNamespace(false), $type->getClassName()); - // xhtml type has too many special cases, do better. - if ($type->getFHIRName() === PHPFHIR_XHTML_TYPE_NAME) { + // few types are handled different. this is lazy and I hate it, but here we are. + if ($type->getFHIRName() === PHPFHIR_XHTML_TYPE_NAME || $type->getKind()->isResourceContainer($version)) { return; } @@ -190,7 +190,13 @@ public static function buildVersionTypeImports(Version $version, Type $type): vo } if ($sourceMeta->isDSTU1()) { - $imports->addCoreFileImportsByName(PHPFHIR_TYPES_INTERFACE_RESOURCE_TYPE); + $imports->addCoreFileImportsByName( + PHPFHIR_TYPES_INTERFACE_RESOURCE_TYPE, + ); + $imports->addVersionCoreFileImportsByName( + $version, + PHPFHIR_VERSION_CLASSNAME_VERSION, + ); } if ($type->getKind()->isResourceContainer($type->getVersion())) { diff --git a/src/Version/SourceMetadata.php b/src/Version/SourceMetadata.php index 2b2325ab..27d09f3a 100644 --- a/src/Version/SourceMetadata.php +++ b/src/Version/SourceMetadata.php @@ -157,22 +157,38 @@ public function getFullPHPFHIRCopyrightComment(): string /** * @return string */ - public function getFHIRGenerationDate(): string + public function getSourceGenerationDate(): string { $this->compile(); return $this->_fhirGenerationDate; } /** - * @param bool $trimmed + * @param bool $trimmed If true, trims off the 'v' prefix before returning. * @return string */ - public function getFHIRVersionString(bool $trimmed): string + public function getSemanticVersion(bool $trimmed): string { $this->compile(); return $trimmed ? trim($this->_fhirVersion, 'v') : $this->_fhirVersion; } + /** + * Return the shortenend representation of the FHIR semantic version containing only Manjor.Minor values. + * + * @return string + */ + public function getShortVersion(): string + { + $this->compile(); + $v = $this->getsemanticVersion(false); + return match (substr_count($v, '.')) { + 1 => $v, + 2 => substr($v, 0, strrpos($v, '.')), + default => implode('.', array_chunk(explode('.', $v), 2)[0]) + }; + } + /** * Returns true if the upstream source was generated from, or is based on, DSTU1. * @@ -180,6 +196,6 @@ public function getFHIRVersionString(bool $trimmed): string */ public function isDSTU1(): bool { - return Semver::satisfies($this->getFHIRVersionString(false), '<= ' . self::_DSTU1_VERSION_MAX); + return Semver::satisfies($this->getSemanticVersion(false), '<= ' . self::_DSTU1_VERSION_MAX); } } \ No newline at end of file diff --git a/template/core/client/class_client.php b/template/core/client/class_client.php index 12535e69..bdfcbc59 100644 --- a/template/core/client/class_client.php +++ b/template/core/client/class_client.php @@ -24,7 +24,6 @@ $coreFiles = $config->getCoreFiles(); $imports = $coreFile->getImports(); - $clientInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_CLIENT_INTERFACE_CLIENT); $clientConfigClass = $coreFiles->getCoreFileByEntityName(PHPFHIR_CLIENT_CLASSNAME_CONFIG); $clientRequestClass = $coreFiles->getCoreFileByEntityName(PHPFHIR_CLIENT_CLASSNAME_REQUEST); @@ -151,10 +150,12 @@ public function exec( $request): err = $err; $rc->errno = $errno; - if ($parseResponseHeaders) { - $rc->resp = substr($resp, $rc->headers->getLength()); - } else { - $rc->resp = $resp; + if (0 === $errno) { + if ($parseResponseHeaders) { + $rc->resp = substr($resp, $rc->headers->getLength()); + } else { + $rc->resp = $resp; + } } return $rc; diff --git a/template/core/encoding/class_resource_parser.php b/template/core/encoding/class_resource_parser.php index 97a7784d..9c12aa44 100644 --- a/template/core/encoding/class_resource_parser.php +++ b/template/core/encoding/class_resource_parser.php @@ -88,7 +88,7 @@ public static function parseArray( $version, arr } if (isset($input[::JSON_FIELD_RESOURCE_TYPE])) { /** @var getFullyQualifiedName(true); ?> $className */ - $className = $version->getTypeMap()::getTypeClassName($input[::JSON_FIELD_RESOURCE_TYPE]); + $className = $version->getTypeMap()::getTypeClassname($input[::JSON_FIELD_RESOURCE_TYPE]); if (null === $className) { throw new \UnexpectedValueException(sprintf( 'Provided input has "%s" value of "%s", but it does not map to any known type. Other keys: ["%s"]', @@ -129,7 +129,7 @@ public static function parseSimpleXMLElement( $v { $elementName = $input->getName(); /** @var getFullyQualifiedName(true); ?> $fhirType */ - $fhirType = $version->getTypeMap()::getTypeClassName($elementName); + $fhirType = $version->getTypeMap()::getTypeClassname($elementName); if (null === $fhirType) { throw new \UnexpectedValueException(sprintf( 'Unable to locate FHIR type for root XML element "%s". Input seen: %s', diff --git a/template/core/types/interface_resource_container_type.php b/template/core/types/interface_resource_container_type.php new file mode 100644 index 00000000..8cfd8ed0 --- /dev/null +++ b/template/core/types/interface_resource_container_type.php @@ -0,0 +1,63 @@ +getCoreFiles(); +$imports = $coreFile->getimports(); + +$typeInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_TYPE); +$containedTypeInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_CONTAINED_TYPE); + +$imports->addCoreFileImports( + $typeInterface, + $containedTypeInterface, +); + +ob_start(); +echo 'declare(strict_types=1); + +namespace getFullyQualifiedNamespace(false); ?>; + +getBasePHPFHIRCopyrightComment(true); ?> + + + +interface extends + +{ + /** + * Must return the contained resource, or null if one is not set. + * + * @return null|getFullyQualifiedName(true); ?> + + */ + public function getContainedType(): null|; + + /** + * Set or unset the contained type. + * + * @param null|getFullyQualifiedName(true); ?> $containedType + * @return static + */ + public function setContainedType(null| $containedType): self; +} + extends { + /** + * Returns the name of the version this type was generated from. + * + * @return string + */ + public function _getFHIRVersionName(): string; + + /** + * Returns the semver of the version of FHIR this type was generated from. + * + * @return string + */ + public function _getFHIRSemanticVersion(): string; + + /** + * Returns the shortened Major.Minor representation of the FHIR semantic version this type was generated from. + * + * @return string + */ + public function _getFHIRShortVersion(): string; + /** * Returns the root XMLNS value found in the source. Null indicates no "xmlns" was found. Only defined when * unserializing XML, and only used when serializing XML. diff --git a/template/core/types/interface_type.php b/template/core/types/interface_type.php index a9ca3587..dad69728 100644 --- a/template/core/types/interface_type.php +++ b/template/core/types/interface_type.php @@ -16,9 +16,14 @@ * limitations under the License. */ +use DCarbone\PHPFHIR\Utilities\ImportUtils; + /** @var \DCarbone\PHPFHIR\Config $config */ /** @var \DCarbone\PHPFHIR\CoreFile $coreFile */ +$coreFiles = $config->getCoreFiles(); +$imports = $coreFile->getImports(); + ob_start(); echo 'declare(strict_types=1); @@ -26,6 +31,8 @@ getBasePHPFHIRCopyrightComment(true); ?> + + interface extends \JsonSerializable { /** @@ -36,8 +43,7 @@ interface extends \JsonSerializable public function _getFHIRTypeName(): string; /** - * Must return associative array where, if there are validation errors, the keys are the names of fields within the - * type that failed validation. The value must be a string message describing the manner of error + * Execute any and all validation rules present on this type and all nested field types. * * @return array */ diff --git a/template/core/validation/class_validator.php b/template/core/validation/class_validator.php index 1ff1a0fc..033c0073 100644 --- a/template/core/validation/class_validator.php +++ b/template/core/validation/class_validator.php @@ -25,9 +25,9 @@ $imports = $coreFile->getImports(); $typeInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_TYPE); -$validationRuleInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_INTERFACE_VALIDATION_RULE); +$validationRuleInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_INTERFACE_RULE); -$enumRuleClass = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_RULE_CLASSNAME_VALUE_ONE_OF); +$valueOneOfRule = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_RULE_CLASSNAME_VALUE_ONE_OF); $minLengthRuleClass = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_RULE_CLASSNAME_VALUE_MIN_LENGTH); $maxLengthRuleClass = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_RULE_CLASSNAME_VALUE_MAX_LENGTH); $patternRuleClass = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_RULE_CLASSNAME_VALUE_PATTERN_MATCH); @@ -38,7 +38,7 @@ $typeInterface, $validationRuleInterface, - $enumRuleClass, + $valueOneOfRule, $minLengthRuleClass, $maxLengthRuleClass, $patternRuleClass, @@ -74,17 +74,28 @@ class */ public static function setRule( $rule): void { - self::_init(); self::$_rules[$rule->getName()] = $rule; } + /** + * Return a rule by name, if it exists + * + * @param string $ruleName + * @return null|getFullyQualifiedName(true); ?> + + */ + public static function getRule(string $ruleName): null| + + { + return self::$_rules[$ruleName] ?? null; + } + /** * Return the current map of rules * @return getFullyQualifiedName(true); ?>[] */ public static function getRules(): array { - self::_init(); return self::$_rules; } @@ -97,31 +108,34 @@ public static function getRules(): array * @return null|string */ public static function runRule( $type, - string $field, - string| $rule, - mixed $constraint, - mixed $value): null|string + string $field, + string| $rule, + mixed $constraint, + mixed $value): null|string { if ($rule instanceof ) { return $rule->assert($type, $field, $constraint, $value); } - self::_init(); if (isset(self::$_rules[$rule])) { return self::$_rules[$rule]->assert($type, $field, $constraint, $value); } + throw new \OutOfBoundsException(sprintf('No rule named "%s" registered.', $rule)); } - private static function _init(): void + public static function _init(): void { if (self::$_initialized) { return; } - self::setRule(new ()); - self::setRule(new ()); - self::setRule(new ()); - self::setRule(new ()); - self::setRule(new ()); - self::setRule(new ()); + self::$_initialized = true; + self::$_rules[::NAME] = new (); + self::$_rules[::NAME] = new (); + self::$_rules[::NAME] = new (); + self::$_rules[::NAME] = new (); + self::$_rules[::NAME] = new (); + self::$_rules[::NAME] = new (); } } + +::_init(); getCoreFiles(); -$validatorClass = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_CLASSNAME_VALIDATOR); $typeInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_TYPE); +$validatorClass = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_CLASSNAME_VALIDATOR); $imports = $coreFile->getImports(); $imports->addCoreFileImports( @@ -65,6 +65,7 @@ public function getDescription(): string; * @param mixed $constraint * @param mixed $value * @return null|string + */ public function assert( $type, string $field, mixed $constraint, mixed $value): null|string; } diff --git a/template/core/validation/rules/class_max_occurs_rule.php b/template/core/validation/rules/class_max_occurs_rule.php index 00984b9b..8cd3b80d 100644 --- a/template/core/validation/rules/class_max_occurs_rule.php +++ b/template/core/validation/rules/class_max_occurs_rule.php @@ -26,13 +26,11 @@ $constantsClass = $coreFiles->getCoreFileByEntityName(PHPFHIR_CLASSNAME_CONSTANTS); $typeInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_TYPE); -$primitiveTypeInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_PRIMITIVE_TYPE); -$validationRuleInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_INTERFACE_VALIDATION_RULE); +$validationRuleInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_INTERFACE_RULE); $imports->addCoreFileImports( $constantsClass, $typeInterface, - $primitiveTypeInterface, $validationRuleInterface, ); @@ -49,7 +47,7 @@ class implements $type, string $field, mixed $constraint, mixed $value): null|string { - if (::UNLIMITED === $constraint || null === $value || [] === $value || $value instanceof ) { + if (::UNLIMITED === $constraint || !is_array($value) || [] === $value) { return null; } $len = count($value); - if ($constraint >= $len) { - return null; + if ($constraint < $len) { + return sprintf('Field "%s" on type "%s" must have no more than %d elements, %d seen', $field, $type->_getFHIRTypeName(), $constraint, $len); } - return sprintf('Field "%s" on type "%s" must have no more than %d elements, %d seen', $field, $type->_getFHIRTypeName(), $constraint, $len); + return null; } } getImports(); $typeInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_TYPE); -$primitiveTypeInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_PRIMITIVE_TYPE); -$validationRuleInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_INTERFACE_VALIDATION_RULE); +$validationRuleInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_INTERFACE_RULE); $imports->addCoreFileImports( $typeInterface, - $primitiveTypeInterface, $validationRuleInterface, ); @@ -47,7 +45,7 @@ class implements $type, string $field, mixed $constraint, mixed $value): null|string { - if (0 >= $constraint || (1 === $constraint && $value instanceof )) { + if (0 >= $constraint || (1 === $constraint && (is_scalar($value) || $value instanceof ))) { return null; } if (null === $value || [] === $value) { diff --git a/template/core/validation/rules/class_value_max_length_rule.php b/template/core/validation/rules/class_value_max_length_rule.php index 53c71eba..0e896abd 100644 --- a/template/core/validation/rules/class_value_max_length_rule.php +++ b/template/core/validation/rules/class_value_max_length_rule.php @@ -26,7 +26,7 @@ $constantsClass = $coreFiles->getCoreFileByEntityName(PHPFHIR_CLASSNAME_CONSTANTS); $typeInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_TYPE); -$validationRuleInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_INTERFACE_VALIDATION_RULE); +$validationRuleInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_INTERFACE_RULE); $imports->addCoreFileImports( $constantsClass, @@ -47,7 +47,7 @@ class implements $type, string $field, mixed return null; } $len = strlen($value); - if ($constraint >= $len) { - return null; + if ($constraint < $len) { + return sprintf('Field "%s" on type "%s" must be no more than %d characters long, %d seen', $field, $type->_getFHIRTypeName(), $constraint, $len); } - return sprintf('Field "%s" on type "%s" must be no more than %d characters long, %d seen', $field, $type->_getFHIRTypeName(), $constraint, $len); + return null; } } getImports(); $typeInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_TYPE); -$validationRuleInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_INTERFACE_VALIDATION_RULE); +$validationRuleInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_INTERFACE_RULE); $imports->addCoreFileImports( $typeInterface, @@ -45,7 +45,7 @@ class implements $type, string $field, mixed } if (null === $value || '' === $value) { return sprintf('Field "%s" on type "%s" must be at least %d characters long, but it is empty', $field, $type->_getFHIRTypeName(), $constraint); + } else if ($constraint > ($len = strlen($value))) { + return sprintf('Field "%s" on type "%s" must be at least %d characters long, %d seen.', $field, $type->_getFHIRTypeName(), $constraint, $len); } - $len = strlen($value); - if ($constraint <= $len) { - return null; - } - return sprintf('Field "%s" on type "%s" must be at least %d characters long, %d seen.', $field, $type->_getFHIRTypeName(), $constraint, $len); + return null; } } getImports(); $typeInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_TYPE); -$validationRuleInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_INTERFACE_VALIDATION_RULE); +$primitiveTypeInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_PRIMITIVE_TYPE); +$validationRuleInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_INTERFACE_RULE); $imports->addCoreFileImports( $typeInterface, + $primitiveTypeInterface, $validationRuleInterface, ); @@ -45,7 +47,7 @@ class implements $type, string $field, mixed $constraint, mixed $value): null|string { - if ([] === $constraint || in_array($value, $constraint, true)) { + if (null === $value || [] === $constraint) { + return null; + } + if ($value instanceof ) { + $value = (string)$value; + } + if (in_array($value, $constraint, true)) { return null; } return sprintf( diff --git a/template/core/validation/rules/class_value_pattern_match_rule.php b/template/core/validation/rules/class_value_pattern_match_rule.php index fe8349cf..4283c4c6 100644 --- a/template/core/validation/rules/class_value_pattern_match_rule.php +++ b/template/core/validation/rules/class_value_pattern_match_rule.php @@ -26,7 +26,7 @@ $typeInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_TYPE); $primitiveTypeInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_PRIMITIVE_TYPE); -$validationRuleInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_INTERFACE_VALIDATION_RULE); +$validationRuleInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_INTERFACE_RULE); $imports->addCoreFileImports( $typeInterface, @@ -47,7 +47,7 @@ class implements $type, string $field, mixed if ($value instanceof ) { $value = (string)$value; } - $res = preg_match($constraint, $value); - if (PREG_NO_ERROR !== preg_last_error()) { - return sprintf( - 'Rule %s failed to verify type "%s" field "%s" value of size %d with pattern "%s": %s', - self::NAME, - $type->_getFHIRTypeName(), - $field, - strlen((string)$value), - $constraint, - preg_last_error_msg(), - ); + try { + $match = preg_match($constraint, $value); + if (PREG_NO_ERROR !== preg_last_error()) { + return sprintf( + 'Rule %s failed to verify type "%s" field "%s" value of size %d with pattern "%s": %s', + self::NAME, + $type->_getFHIRTypeName(), + $field, + strlen((string)$value), + $constraint, + preg_last_error_msg(), + ); + } else if (!$match) { + return sprintf('Field "%s" on type "%s" value of "%s" does not match pattern: %s', $field, $type->_getFHIRTypeName(), $value, $constraint); + } + } catch (\Exception $e) { + return sprintf('Rule %s failed to verify type "%s" field "%s" value with pattern "%s": %s', self::NAME, $type->_getFHIRTypeName(), $field, $constraint, $e->getMessage()); } - if ($res) { - return null; - } - return sprintf('Field "%s" on type "%s" value of "%s" does not match pattern: %s', $field, $type->_getFHIRTypeName(), $value, $constraint); + return null; } } getCoreFiles(); -$validatorClass = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_CLASSNAME_VALIDATOR); +$imports = $coreFile->getImports(); + $typeInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_TYPE); +$validatorClass = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_CLASSNAME_VALIDATOR); -$imports = $coreFile->getImports(); $imports->addCoreFileImports( $typeInterface, $validatorClass, @@ -122,33 +123,34 @@ public function _setFieldValidationRule(string $field, string $rule, mixed $cons * * @return array */ - public function _getValidationErrors(): array + public function _getValidationErrors(): array { + $rules = $this->_getCombinedValidationRules(); $errs = []; - foreach ($this->_getCombinedValidationRules() as $field => $rules) { - $v = $this->{$field} ?? null; - foreach ($rules as $rule => $constraint) { - $err = ::runRule($this, $field, $rule, $constraint, $v); - if (null !== $err) { - if (!isset($errs[$field])) { - $errs[$field] = []; + foreach ($this as $prop => $value) { + if (str_starts_with($prop, '_')) { + continue; + } + if (isset($rules[$prop])) { + foreach ($rules[$prop] as $rule => $constraint) { + $err = ::runRule($this, $prop, $rule, $constraint, $value); + if (null !== $err) { + if (!isset($errs[$prop])) { + $errs[$prop] = []; + } + $errs[$prop][$rule] = $err; } - $errs[$field][] = $err; } } - if ($v instanceof ) { - $typeErrs = $v->_getValidationErrors(); - if ([] !== $typeErrs) { - foreach($typeErrs as $subField => $subErrs) { - $errs["{$field}.{$subField}"] = $subErrs; - } + if ($value instanceof ) { + foreach ($value->_getValidationErrors() as $subPath => $subErrs) { + $errs["{$prop}.{$subPath}"] = $subErrs; } - } else if (is_array($v)) { - foreach($v as $i => $vv) { - $typeErrs = $vv->_getValidationErrors(); - if ([] !== $typeErrs) { - foreach($typeErrs as $subField => $subErrs) { - $errs["{$field}.{$i}.{$subField}"] = $subErrs; + } else if (is_array($value)) { + foreach($value as $i => $vv) { + if ($vv instanceof ) { + foreach ($vv->_getValidationErrors() as $subPath => $subErrs) { + $errs["{$prop}.{$i}.{$subPath}"] = $subErrs; } } } diff --git a/template/core/versions/interface_version.php b/template/core/versions/interface_version.php index d27244a3..8bebe283 100644 --- a/template/core/versions/interface_version.php +++ b/template/core/versions/interface_version.php @@ -57,14 +57,21 @@ public function getName(): string; * * @return string */ - public function getSourceVersion(): string; + public function getFHIRSemanticVersion(): string; + + /* + * Must return the shortened Major.Minor representation of the source's semantic version. + * + * @return string + */ + public function getFHIRShortVersion(): string; /** * Must return the date this FHIR version's source was generated * * @return string */ - public function getSourceGenerationDate(): string; + public function getFHIRGenerationDate(): string; /** * Must return config for this version diff --git a/template/core/versions/interface_version_type_map.php b/template/core/versions/interface_version_type_map.php index ba4151e5..e97d97f7 100644 --- a/template/core/versions/interface_version_type_map.php +++ b/template/core/versions/interface_version_type_map.php @@ -43,14 +43,6 @@ interface { - /** - * Must return the fully qualified class name for FHIR Type name. Must return null if type not found. - * - * @param string $typeName - * @return string|null - */ - public static function getTypeClassName(string $typeName): null|string; - /** * Must return the full internal class map * @@ -66,30 +58,40 @@ public static function getMap(): array; public static function getContainableTypes(): array; /** - * @param string $typeName Name of FHIR object reference by a version's container type + * Must return the fully qualified class name for FHIR Type name. Must return null if type not found. + * + * @param string|\stdClass|\SimpleXMLElement $input Must expect either name of type, or unserialized JSON or XML. + * @return string|null + */ + public static function getTypeClassname(string|\stdClass|\SimpleXMLElement $input): null|string; + + /** + * Must attempt to return the fully qualified classname of a contained type from the provided input, if it + * if it represents one. + * + * @param string|\stdClass|\SimpleXMLElement $input Expects either name of type or unserialized JSON or XML. * @return string|null Name of class as string or null if type is not contained in map */ - public static function getContainedTypeClassName(string $typeName): null|string; + public static function getContainedTypeClassname(string|\stdClass|\SimpleXMLElement $input): null|string; /** * Must attempt to determine if the provided value is or describes a containable resource type * - * @param string|array|\SimpleXMLElement|getFullyQualifiedName(true); ?> $type + * @param string|\stdClass|\SimpleXMLElement|getFullyQualifiedName(true); ?> $input * @return bool - * @throws \InvalidArgumentException */ - public static function isContainableResource(string|array|\SimpleXMLElement| $type): bool; + public static function isContainableType(string|\stdClass|\SimpleXMLElement|TypeInterface $input): bool; /** * @param \SimpleXMLElement $node Parent element containing inline resource * @return string Fully qualified class name of contained resource type */ - public static function getContainedTypeClassNameFromXML(\SimpleXMLElement $node): string; + public static function mustGetContainedTypeClassnameFromXML(\SimpleXMLElement $node): string; /** * @param \stdClass $json * @return string Fully qualified class name of contained resource type */ - public static function getContainedTypeClassNameFromJSON(\stdClass $json): string; + public static function mustGetContainedTypeClassnameFromJSON(\stdClass $json): string; } getCoreFiles(); +$testCoreFiles = $config->getCoreTestFiles(); +$imports = $coreFile->getImports(); + +$elementTypeInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_ELEMENT_TYPE); +$commentContainerInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_COMMENT_CONTAINER); +$commentContainerTrait = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_TRAIT_COMMENT_CONTAINER); + +$typeValidationTrait = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_TRAIT_TYPE_VALIDATIONS); + +$jsonSerializableOptionsTrait = $coreFiles->getCoreFileByEntityName(PHPFHIR_ENCODING_TRAIT_JSON_SERIALIZATION_OPTIONS); +$xmlSerializationOptionsTrait = $coreFiles->getCoreFileByEntityName(PHPFHIR_ENCODING_TRAIT_XML_SERIALIZATION_OPTIONS); +$xmlWriterClass = $coreFiles->getCoreFileByEntityName(PHPFHIR_ENCODING_CLASSNAME_XML_WRITER); +$unserializeConfig = $coreFiles->getCoreFileByEntityName(PHPFHIR_ENCODING_CLASSNAME_UNSERIALIZE_CONFIG); +$serializeConfig = $coreFiles->getCoreFileByEntityName(PHPFHIR_ENCODING_CLASSNAME_SERIALIZE_CONFIG); + +$mockTypeFieldsTrait = $testCoreFiles->getCoreFileByEntityName(PHPFHIR_TEST_TRAIT_MOCK_TYPE_FIELDS); + +$imports->addCoreFileImports( + $elementTypeInterface, + $commentContainerInterface, + $commentContainerTrait, + + $typeValidationTrait, + + $jsonSerializableOptionsTrait, + $xmlSerializationOptionsTrait, + $xmlWriterClass, + $unserializeConfig, + $serializeConfig, + + $mockTypeFieldsTrait, +); + +ob_start(); +echo ' declare(strict_types=1); + +namespace getFullyQualifiedNamespace(false); ?>; + +getBasePHPFHIRCopyrightComment(true); ?> + + + +class implements , , \Iterator + +{ + use , + , + , + , + ; + + private const _FHIR_VALIDATION_RULES = []; + + protected string $_name; + + private array $_valueXMLLocations = []; + + public function __construct(string $name, + array $fields = [], + array $validationRuleMap = [], + array $fhirComments = []) + { + $this->_name = $name; + $this->_setFHIRComments($fhirComments); + foreach($validationRuleMap as $field => $rules) { + $this->_setFieldValidationRules($field, $rules); + } + $this->_processFields($fields); + } + + public function _getFHIRTypeName(): string + { + return $this->_name; + } + + public static function xmlUnserialize(\SimpleXMLElement $element, + $config, + null| $type = null): self + { + throw new \BadMethodCallException('xmlUnserialize not yet implemented'); + } + + public function xmlSerialize( $xw, + $config): void + { + $this->_xmlSerialize($xw, $config); + } + + public static function jsonUnserialize(\stdClass $json, + $config, + null| $type = null): self + { + throw new \BadMethodCallException('jsonUnserialize not yet implemented'); + } + + public function __toString(): string + { + return $this->_name; + } +} +getCoreFiles(); +$testCoreFiles = $config->getCoreTestFiles(); +$imports = $coreFile->getImports(); + +$primitiveInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_PRIMITIVE_TYPE); +$primitiveContainerInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_PRIMITIVE_CONTAINER_TYPE); + +$typeValidationTrait = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_TRAIT_TYPE_VALIDATIONS); + +$valueXMLLocationEnum = $coreFiles->getCoreFileByEntityName(PHPFHIR_ENCODING_ENUM_VALUE_XML_LOCATION); +$jsonSerializableOptionsTrait = $coreFiles->getCoreFileByEntityName(PHPFHIR_ENCODING_TRAIT_JSON_SERIALIZATION_OPTIONS); +$xmlSerializationOptionsTrait = $coreFiles->getCoreFileByEntityName(PHPFHIR_ENCODING_TRAIT_XML_SERIALIZATION_OPTIONS); +$xmlWriterClass = $coreFiles->getCoreFileByEntityName(PHPFHIR_ENCODING_CLASSNAME_XML_WRITER); +$serializeConfig = $coreFiles->getCoreFileByEntityName(PHPFHIR_ENCODING_CLASSNAME_SERIALIZE_CONFIG); + +$mockTypeFieldsTrait = $testCoreFiles->getCoreFileByEntityName(PHPFHIR_TEST_TRAIT_MOCK_TYPE_FIELDS); +$mockElementTypeClass = $testCoreFiles->getCoreFileByEntityName(PHPFHIR_TEST_CLASSNAME_MOCK_ELEMENT_TYPE); + +$imports->addCoreFileImports( + $primitiveInterface, + $primitiveContainerInterface, + + $typeValidationTrait, + + $valueXMLLocationEnum, + $jsonSerializableOptionsTrait, + $xmlSerializationOptionsTrait, + $xmlWriterClass, + $serializeConfig, + + $mockTypeFieldsTrait, + $mockElementTypeClass, +); + +ob_start(); +echo ' declare(strict_types=1); + +namespace getFullyQualifiedNamespace(false); ?>; + +getBasePHPFHIRCopyrightComment(true); ?> + + + +class extends implements + +{ + use , + , + , + ; + + + private const _FHIR_VALIDATION_RULES = []; + + private array $_valueXMLLocations = []; + + public function __construct(string $name, + array $fields = [], + array $validationRuleMap = [], + array $fhirComments = [], + mixed $value = null) + { + if (!isset($fields['value']) + || !isset($fields['value']['class']) + || !is_a($fields['value']['class'], ::class, true)) { + throw new \InvalidArgumentException(sprintf( + 'Primitive container type "%s" must have a "value" field and it must be a primitive type.', + $name, + )); + } + if (null !== $value) { + $fields['value']['value'] = $value; + } + parent::__construct($name, $fields, $validationRuleMap, $fhirComments); + } + + public function _nonValueFieldDefined(): bool + { + foreach($this->_fields as $field => $def) { + if ('value' !== $field && isset($def['value']) && [] !== $def['value']) { + return true; + } + } + return false; + } + + public function xmlSerialize( $xw, + $config, + null| $valueLocation = null): void + { + $this->_xmlSerialize($xw, $config, $valueLocation); + } + + public function _getValueAsString(): string + { + return (string)$this->getValue(); + } +} +getCoreFiles(); +$testCoreFiles = $config->getCoreTestFiles(); +$imports = $coreFile->getImports(); -$typeInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_TYPE); -$primitiveTypeInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_PRIMITIVE_TYPE); -$primitiveContainerInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_PRIMITIVE_CONTAINER_TYPE); $resourceTypeInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_RESOURCE_TYPE); $commentContainerInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_COMMENT_CONTAINER); $commentContainerTrait = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_TRAIT_COMMENT_CONTAINER); @@ -33,18 +32,15 @@ $typeValidationTrait = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_TRAIT_TYPE_VALIDATIONS); -$valueXMLLocationEnum = $coreFiles->getCoreFileByEntityName(PHPFHIR_ENCODING_ENUM_VALUE_XML_LOCATION); $jsonSerializableOptionsTrait = $coreFiles->getCoreFileByEntityName(PHPFHIR_ENCODING_TRAIT_JSON_SERIALIZATION_OPTIONS); $xmlSerializationOptionsTrait = $coreFiles->getCoreFileByEntityName(PHPFHIR_ENCODING_TRAIT_XML_SERIALIZATION_OPTIONS); $xmlWriterClass = $coreFiles->getCoreFileByEntityName(PHPFHIR_ENCODING_CLASSNAME_XML_WRITER); $unserializeConfig = $coreFiles->getCoreFileByEntityName(PHPFHIR_ENCODING_CLASSNAME_UNSERIALIZE_CONFIG); $serializeConfig = $coreFiles->getCoreFileByEntityName(PHPFHIR_ENCODING_CLASSNAME_SERIALIZE_CONFIG); -$imports = $coreFile->getImports(); +$mockTypeFieldsTrait = $testCoreFiles->getCoreFileByEntityName(PHPFHIR_TEST_TRAIT_MOCK_TYPE_FIELDS); + $imports->addCoreFileImports( - $typeInterface, - $primitiveTypeInterface, - $primitiveContainerInterface, $resourceTypeInterface, $commentContainerInterface, $commentContainerTrait, @@ -52,58 +48,57 @@ $typeValidationTrait, - $valueXMLLocationEnum, $jsonSerializableOptionsTrait, $xmlSerializationOptionsTrait, $xmlWriterClass, $unserializeConfig, $serializeConfig, + + $mockTypeFieldsTrait, ); ob_start(); -echo " +echo ' declare(strict_types=1); + namespace getFullyQualifiedNamespace(false); ?>; getBasePHPFHIRCopyrightComment(true); ?> -class implements , +class implements , , \Iterator { use , , , , - ; + , + ; private const _FHIR_VALIDATION_RULES = []; protected string $_name; - protected array $_fields = []; + protected string $_versionName; + protected string $_semanticVersion; private array $_valueXMLLocations = []; public function __construct(string $name, array $fields = [], array $validationRuleMap = [], - array $fhirComments = []) + array $fhirComments = [], + string $versionName = 'mock', + string $semanticVersion = 'v0.0.0') { $this->_name = $name; - $this->_fields = $fields; + $this->_versionName = $versionName; + $this->_semanticVersion = $semanticVersion; $this->_setFHIRComments($fhirComments); foreach($validationRuleMap as $field => $rules) { - $this->_setFieldValidationRules($field, $validationRuleMap); - } - - foreach($fields as $field => $def) { - if (!isset($def['class'])) { - throw new \LogicException(sprintf('Field "%s" definition must contain "class" key', $field)); - } - if (is_a($def['class'], ::class, true)) { - $this->_valueXMLLocations[$field] = ::CONTAINER_ATTRIBUTE; - } + $this->_setFieldValidationRules($field, $rules); } + $this->_processFields($fields); } public function _getFHIRTypeName(): string @@ -111,120 +106,32 @@ public function _getFHIRTypeName(): string return $this->_name; } - protected function _doGet(string $field, array $fieldDef, array $args): null|array| - - { - if ([] !== $args) { - throw new \BadMethodCallException(sprintf('Method "get%s" has no parameters, but %d were provided', ucfirst($field), count($args))); - } - $collection = $fieldDef['collection'] ?? false; - return $this->_fields[$field]['value'] ?? ($collection ? [] : null); - } - - protected function _doSet(string $field, array $fieldDef, array $args): self + public function _getFHIRVersionName(): string { - $class = $fieldDef['class']; - $collection = $fieldDef['collection'] ?? false; - $primitive = is_a($class, ::class, true); - - // non-collection setters accept exactly 1 argument - if (!$collection && 1 !== count($args)) { - throw new \BadMethodCallException(sprintf('Method "set%s" must have exactly one argument of type %s', ucfirst($field), $class)); - } - - // if "empty" input, unset value - if (([] === $args && $collection) || null === $args[0]) { - unset($this->_fields[$field]['value']); - return $this; - } - - if ($collection) { - $this->_fields[$field]['value'] = []; - foreach($args as $v) { - if ($primitive && (is_scalar($v) || $v instanceof \DateTime)) { - $this->_fields[$field]['value'][] = new $class($v); - } else if (!is_a($v, $class, false)) { - throw new \InvalidArgumentException(sprintf('Field "%s" values must be of type "%s", saw "%s"', $field, $class, gettype($v))); - } else { - $this->_fields[$field]['value'][] = $v; - } - } - return $this; - } - - if ($primitive && (is_scalar($args[0]) || $args[0] instanceof \DateTime)) { - $this->_fields[$field] = new $class($args[0]); - } else if (!is_a($args[0], $class, false)) { - throw new \InvalidArgumentException(sprintf('Field "%s" value must be of type "%s", saw "%s"', $field, $class, gettype($args[0]))); - } else { - $this->_fields[$field]['value'] = $args[0]; - } - - return $this; + return $this->_versionName; } - protected function _doAdd(string $field, array $fieldDef, array $args): self + public function _getFHIRSemanticVersion(): string { - $class = $fieldDef['class']; - $collection = $fieldDef['collection'] ?? false; - $primitive = is_a($class, ::class, true); - - if (!$collection) { - throw new \BadMethodCallException(sprintf('Method "add%s" not defined', ucfirst($field))); - } - - // collection add methods have exactly 1 parameter. - if (1 !== count($args)) { - throw new \InvalidArgumentException(sprintf('Method "add%s" requires exactly 1 parameter, but %d were provided.', ucfirst($field), count($args))); - } - - if ($primitive && (is_scalar($args[0]) || $args[0] instanceof \DateTime)) { - if (!isset($this->_fields[$field]['value'])) { - $this->_fields[$field]['value'] = []; - } - $this->_fields[$field]['value'][] = new $class($args[0]); - return $this; - } - - if (!is_a($args[0], $class, false)) { - throw new \InvalidArgumentException(sprintf('Field "%s" value must be of type "%s", saw "%s"', $field, $class, gettype($args[0]))); - } - - if (!isset($this->_fields[$field]['value'])) { - $this->_fields[$field]['value'] = []; - } - $this->_fields[$field]['value'][] = $args[0]; - - return $this; + return $this->_semanticVersion; } - public function __call(string $name, array $args): null|self| - + public function _getFHIRShortVersion(): string { - $get = str_starts_with($name, 'get'); - $set = str_starts_with($name, 'set'); - $add = str_starts_with($name, 'add'); - - if (!$get && !$set && !$add) { - throw new \BadMethodCallException(sprintf('Method "%s" not defined', $name)); - } - - $field = lcfirst(substr($name, 3)); - if (!isset($this->_fields[$field])) { - throw new \BadMethodCallException(sprintf('No field "%s" defined', $field)); - } - - return match(true) { - $get => $this->_doGet($field, $this->_fields[$field], $args), - $set => $this->_doSet($field, $this->_fields[$field], $args), - $add => $this->_doAdd($field, $this->_fields[$field], $args), + $v = ltrim($this->_semanticVersion, 'v'); + return match (substr_count($v, '.')) { + 1 => $v, + 2 => substr($v, 0, strrpos($v, '.')), + default => implode('.', array_chunk(explode('.', $v), 2)[0]) }; } - public static function xmlUnserialize(\SimpleXMLElement|string $element, $config = null, $type = null): + public static function xmlUnserialize(\SimpleXMLElement|string $element, + null| $config = null, + null| $type = null): { - throw new \BadMethodCallException('gotta do this'); + throw new \BadMethodCallException('xmlUnserialize not yet implemented'); } public function xmlSerialize(null| $xw = null, null| $config = null): @@ -248,46 +155,7 @@ public function xmlSerialize(null| $xw = null, nul $xw->openRootNode($this->_name, $this->_getSourceXMLNS()); } - // define primitives as attributes - foreach($this->_fields as $field => $def) { - $class = $def['class']; - $value = $def['value'] ?? null; - $primitive = is_a($class, ::class, true); - - if (!$primitive || null === $value) { - continue; - } - - $xw->writeAttribute($field, $value->_getValueAsString()); - } - - // define others as elements - foreach($this->_fields as $field => $def) { - $class = $def['class']; - $value = $def['value'] ?? null; - $collection = $def['collection'] ?? false; - $primitiveContainer = is_a($class, ::class, true); - - if ($collection) { - foreach ($value as $v) { - $xw->startElement($field); - if ($primitiveContainer) { - $v->xmlSerialize($xw, $config, $this->_valueXMLLocations[$field]); - } else { - $v->xmlSerialize($xw, $config); - } - $xw->endElement(); - } - } else if ($primitiveContainer) { - $xw->startElement($field); - $value->xmlSerialize($xw, $config, $this->_valueXMLLocations[$field]); - $xw->endElement(); - } else { - $xw->startElement($field); - $value->xmlSerialize($xw, $config); - $xw->endElement(); - } - } + $this->_xmlSerialize($xw, $config); if ($rootOpened ?? false) { $xw->endElement(); @@ -301,29 +169,7 @@ public function xmlSerialize(null| $xw = null, nul public static function jsonUnserialize(string|\stdClass $json, null| $config = null, null| $type = null): { - throw new \BadMethodCallException('gotta do this'); - } - - public function jsonSerialize(): \stdClass - { - $out = new \stdClass(); - foreach($this->_fields as $field => $def) { - $class = $def['class']; - $value = $def['value'] ?? null; - $primitiveContainer = is_a($class, ::class, true); - - if (null === $value || [] === $value) { - continue; - } - - if (!$primitiveContainer) { - $out->{$field} = $value; - } else { - $out->{$field}->getValue(); - } - } - - return $out; + throw new \BadMethodCallException('jsonUnserialize not yet implemented'); } public function __toString(): string diff --git a/template/tests/core/types/class_mock_string_primitive_type.php b/template/tests/core/types/class_mock_string_primitive_type.php index caaa0e43..8a4d6866 100644 --- a/template/tests/core/types/class_mock_string_primitive_type.php +++ b/template/tests/core/types/class_mock_string_primitive_type.php @@ -37,7 +37,8 @@ ); ob_start(); -echo " +echo ' declare(strict_types=1); + namespace getFullyQualifiedNamespace(false); ?>; getBasePHPFHIRCopyrightComment(true); ?> @@ -57,14 +58,14 @@ class implements protected string $value; - public function __construct(string $typeName, + public function __construct(string $name = 'mock-string-primitive', null|string $value = null, array $validationRuleMap = []) { - $this->_name = $typeName; + $this->_name = $name; $this->setValue($value); foreach($validationRuleMap as $field => $rules) { - $this->_setFieldValidationRules($field, $validationRuleMap); + $this->_setFieldValidationRules($field, $rules); } } diff --git a/template/tests/core/types/trait_mock_type_fields.php b/template/tests/core/types/trait_mock_type_fields.php new file mode 100644 index 00000000..51e89fbe --- /dev/null +++ b/template/tests/core/types/trait_mock_type_fields.php @@ -0,0 +1,452 @@ +getCoreFiles(); +$imports = $coreFile->getImports(); + +$typeInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_TYPE); +$elementTypeInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_ELEMENT_TYPE); +$primitiveTypeInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_PRIMITIVE_TYPE); +$primitiveContainerInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_PRIMITIVE_CONTAINER_TYPE); +$elementTypeInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_ELEMENT_TYPE); + +$valueXMLLocationEnum = $coreFiles->getCoreFileByEntityName(PHPFHIR_ENCODING_ENUM_VALUE_XML_LOCATION); +$xmlWriterClass = $coreFiles->getCoreFileByEntityName(PHPFHIR_ENCODING_CLASSNAME_XML_WRITER); +$serializeConfig = $coreFiles->getCoreFileByEntityName(PHPFHIR_ENCODING_CLASSNAME_SERIALIZE_CONFIG); + +$imports->addCoreFileImports( + $typeInterface, + $primitiveTypeInterface, + $primitiveContainerInterface, + $elementTypeInterface, + + $valueXMLLocationEnum, + $xmlWriterClass, + $serializeConfig, +); + +ob_start(); +echo ' declare(strict_types=1); + +namespace getFullyQualifiedNamespace(false); ?>; + +getBasePHPFHIRCopyrightComment(true); ?> + + + +trait + +{ + protected array $_fields = []; + + public function __call(string $name, array $args): null|self|iterable| + + { + $get = str_starts_with($name, 'get'); + $set = str_starts_with($name, 'set'); + $add = str_starts_with($name, 'add'); + + if (!$get && !$set && !$add) { + throw new \BadMethodCallException(sprintf('Method "%s" not defined', $name)); + } + + $field = lcfirst(substr($name, 3)); + if (!isset($this->_fields[$field])) { + throw new \BadMethodCallException(sprintf('No field "%s" defined', $field)); + } + + return match(true) { + $get => $this->_doGet($field, $this->_fields[$field], $args), + $set => $this->_doSet($field, $this->_fields[$field], $args), + $add => $this->_doAdd($field, $this->_fields[$field], $args), + }; + } + + public function __isset(string $name): bool + { + return isset($this->_fields[$name]); + } + + public function __get(string $name): array| + + { + return $this->_fields[$name]['value']; + } + + public function current(): mixed + { + $def = current($this->_fields); + return $def['value'] ?? null; + } + + public function key(): mixed + { + return key($this->_fields); + } + + public function next(): void + { + next($this->_fields); + } + + public function rewind(): void + { + reset($this->_fields); + } + + public function valid(): bool + { + return null !== key($this->_fields); + } + + /** + * Process any / all field definitions for the mock type, ensuring they're probably sane. + * + * @param array $fields + */ + protected function _processFields(array $fields): void + { + // compute this once. + $mockElement = ($this instanceof ); + + // process field declarations, performing some basic value validation and processing. + foreach($fields as $field => $def) { + // lazy check for "sane" field name, probably not good enough. + if (!preg_match('{^[a-zA-Z0-9]+}', $field)) { + throw new \DomainException(sprintf('Field name "%s" is not valid.', $field)); + } + + // must have a class set. + if (!isset($def['class'])) { + throw new \LogicException(sprintf('Field "%s" definition must contain "class" key', $field)); + } + + // all fields must implement the base type interface. + if (!is_string($def['class']) || !is_a($def['class'], ::class, true)) { + throw new \InvalidArgumentException(sprintf( + 'Type "%s" field "%s" class "%s" does not implement required interface "%s"', + $this->_name, + $field, + is_string($def['class']) ? $def['class'] : gettype($def['class']), + ::class, + )); + } + + // localize a few things to make life easier. + $class = $def['class']; + $value = $def['value'] ?? null; + $collection = $def['collection'] ?? false; + + // check for field types. + $primitive = is_a($class, ::class, true); + $element = is_a($class, ::class, true); + $primitiveContainer = is_a($class, ::class, true); + + // element types may only have other element types or primitives as field types. + if ($mockElement && !$element && !$primitive) { + throw new \InvalidArgumentException(sprintf( + 'Mock element type "%s" may only have other elements or primitives field types but field "%s" has class "%s"', + $this->_name, + $field, + $class, + )); + } + + // if value is unset / null, move on to next field + if (null === $value) { + continue; + } + + // start collection field processing + if ($collection) { + // if you wish to set an initial value for a collection field, you must provide an array of values. + if (!is_array($value)) { + throw new \InvalidArgumentException(sprintf( + 'Must leave mock type "%s" collection field "%s" value unset or provide an array of values', + $this->_name, + $class + )); + } + foreach($value as $i => $v) { + // if we have the expected field class type, move on. + if (is_a($v, $class, false)) { + continue; + } + // nulls and anything other than scalar values are prohibited at this point. + if (!is_scalar($v)) { + throw new \InvalidArgumentException(sprintf( + 'Unexpected value of php type "%s" provided at offset %d of mock type "%s" collection field "%s" value.', + gettype($v), + $i, + $this->_name, + $field, + )); + } + // if this is a primitive or primitive container, set the initial value to be the type instance. + if ($primitive || $primitiveContainer) { + $fields[$field]['value'][$i] = new $class(value: $v); + continue; + } + // all other types must be instances + throw new \InvalidArgumentException(sprintf( + 'Must provide fully initialized instance of type "%s" to all values for mock type "%s" collection field "%s"', + $class, + $this->_name, + $field, + )); + } + + // end collection field processing + continue; + } + + // ensure the appropriate XML locations are set. + if ($primitive) { + $this->_valueXMLLocations[$field] = ::PARENT_ATTRIBUTE; + } else if ($primitiveContainer) { + $this->_valueXMLLocations[$field] = ::CONTAINER_ATTRIBUTE; + } + + // if not set or already the correct instance, move on. + if ($value instanceof $class) { + continue; + } + + // non-primitives _must_ have an instance of the appropriate type set as their initial value, if one is set. + if (!$primitive && !$primitiveContainer) { + throw new \InvalidArgumentException(sprintf( + 'Must provide instance of "%s" to mock type "%s" field "%s" value.', + $class, + $this->_name, + $field, + )); + } + + // this point, the value must be a scalar. + if (!is_scalar($value)) { + throw new \InvalidArgumentException(sprintf( + 'Unexpected value of php type "%s" provided to mock type "%s" field "%s" value.', + gettype($value), + $this->_name, + $field, + )); + } + + $fields[$field]['value'] = new $class(value: $value); + } + + // finally set fields + $this->_fields = $fields; + } + + protected function _doGet(string $field, array $fieldDef, array $args): null|array| + + { + if ([] !== $args) { + throw new \BadMethodCallException(sprintf('Method "get%s" has no parameters, but %d were provided', ucfirst($field), count($args))); + } + $collection = $fieldDef['collection'] ?? false; + return $this->_fields[$field]['value'] ?? ($collection ? [] : null); + } + + protected function _doSet(string $field, array $fieldDef, array $args): self + { + $class = $fieldDef['class']; + $collection = $fieldDef['collection'] ?? false; + $primitive = is_a($class, ::class, true); + + // non-collection setters accept exactly 1 argument + if (!$collection && 1 !== count($args)) { + throw new \BadMethodCallException(sprintf('Method "set%s" must have exactly one argument of type %s', ucfirst($field), $class)); + } + + // if "empty" input, unset value + if (([] === $args && $collection) || null === $args[0]) { + unset($this->_fields[$field]['value']); + return $this; + } + + if ($collection) { + $this->_fields[$field]['value'] = []; + foreach($args as $v) { + if ($primitive && (is_scalar($v) || $v instanceof \DateTime)) { + $this->_fields[$field]['value'][] = new $class($v); + } else if (!is_a($v, $class, false)) { + throw new \InvalidArgumentException(sprintf('Field "%s" values must be of type "%s", saw "%s"', $field, $class, gettype($v))); + } else { + $this->_fields[$field]['value'][] = $v; + } + } + return $this; + } + + if ($primitive && (is_scalar($args[0]) || $args[0] instanceof \DateTime)) { + $this->_fields[$field] = new $class($args[0]); + } else if (!is_a($args[0], $class, false)) { + throw new \InvalidArgumentException(sprintf('Field "%s" value must be of type "%s", saw "%s"', $field, $class, gettype($args[0]))); + } else { + $this->_fields[$field]['value'] = $args[0]; + } + + return $this; + } + + protected function _doAdd(string $field, array $fieldDef, array $args): self + { + $class = $fieldDef['class']; + $collection = $fieldDef['collection'] ?? false; + $primitive = is_a($class, ::class, true); + + if (!$collection) { + throw new \BadMethodCallException(sprintf('Method "add%s" not defined', ucfirst($field))); + } + + // collection add methods have exactly 1 parameter. + if (1 !== count($args)) { + throw new \InvalidArgumentException(sprintf('Method "add%s" requires exactly 1 parameter, but %d were provided.', ucfirst($field), count($args))); + } + + if ($primitive && (is_scalar($args[0]) || $args[0] instanceof \DateTime)) { + if (!isset($this->_fields[$field]['value'])) { + $this->_fields[$field]['value'] = []; + } + $this->_fields[$field]['value'][] = new $class($args[0]); + return $this; + } + + if (!is_a($args[0], $class, false)) { + throw new \InvalidArgumentException(sprintf('Field "%s" value must be of type "%s", saw "%s"', $field, $class, gettype($args[0]))); + } + + if (!isset($this->_fields[$field]['value'])) { + $this->_fields[$field]['value'] = []; + } + $this->_fields[$field]['value'][] = $args[0]; + + return $this; + } + + protected function _xmlSerialize( $xw, + $config, + null| $valueLocation = null): void + { + $mockPrimitiveContainer = ($this instanceof ); + + // if this is a mock primitve container, we need to handle locations a lil' different + if ($mockPrimitiveContainer) { + $valueLocation = $valueLocation ?? $this->_valueXMLLocations['value']; + } + + // handle attribute serialization + foreach($this->_fields as $field => $def) { + $class = $def['class']; + $value = $def['value'] ?? null; + $primitive = is_a($class, ::class, true); + $primitiveContainer = is_a($class, ::class, true); + + if (null === $value || (!$primitive && !$primitiveContainer)) { + continue; + } + + // primitive containers may only have their value field as an attribute + if ($mockPrimitiveContainer) { + if ('value' !== $field || ::CONTAINER_ATTRIBUTE !== $valueLocation) { + continue; + } + } else { + if ($primitiveContainer && ::PARENT_ATTRIBUTE === $this->_valueXMLLocations[$field]) { + continue; + } + } + + $xw->writeAttribute($field, $value->_getValueAsString()); + } + + // define others as elements + foreach($this->_fields as $field => $def) { + $class = $def['class']; + $value = $def['value'] ?? null; + $collection = $def['collection'] ?? false; + $primitiveContainer = is_a($class, ::class, true); + + if (null === $value) { + continue; + } + + if ($collection) { + foreach ($value as $v) { + $xw->startElement($field); + if ($primitiveContainer) { + $v->xmlSerialize($xw, $config, $this->_valueXMLLocations[$field]); + } else { + $v->xmlSerialize($xw, $config); + } + $xw->endElement(); + } + } else if ($mockPrimitiveContainer && 'value' === $field) { + if (::CONTAINER_VALUE === $valueLocation) { + $xw->text((string)$this->getValue()); + } else if (::ELEMENT_ATTRIBUTE === $valueLocation) { + $xw->startElement('value'); + $xw->writeAttribute('value', (string)$this->getValue()); + $xw->endElement(); + } else if (::ELEMENT_VALUE === $valueLocation) { + $xw->writeElement('value', (string)$this->getValue()); + } + } else if ($primitiveContainer) { + if ($value->_nonValueFieldDefined() || ::PARENT_ATTRIBUTE !== $this->_valueXMLLocations[$field]) { + $xw->startElement($field); + $value->xmlSerialize($xw, $config, $this->_valueXMLLocations[$field]); + $xw->endElement(); + } + } else { + $xw->startElement($field); + $value->xmlSerialize($xw, $config); + $xw->endElement(); + } + } + } + + public function jsonSerialize(): \stdClass + { + $out = new \stdClass(); + foreach($this->_fields as $field => $def) { + $class = $def['class']; + $value = $def['value'] ?? null; + $primitiveContainer = is_a($class, ::class, true); + + if (null === $value) { + continue; + } + + if (!$primitiveContainer) { + $out->{$field} = $value; + } else { + $out->{$field}->getValue(); + } + } + + return $out; + } +} +getCoreFiles(); +$maxOccursRule = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_RULE_CLASSNAME_MAX_OCCURS); + +$testCoreFiles = $config->getCoreTestFiles(); +$mockResource = $testCoreFiles->getCoreFileByEntityName(PHPFHIR_TEST_CLASSNAME_MOCK_RESOURCE_TYPE); +$mockPrimitive = $testCoreFiles->getCoreFileByEntityName(PHPFHIR_TEST_CLASSNAME_MOCK_STRING_PRIMITIVE_TYPE); + +$imports = $coreFile->getImports(); +$imports->addCoreFileImports( + $mockResource, + $mockPrimitive, + $maxOccursRule, +); + +ob_start(); +echo " +namespace getFullyQualifiedNamespace(false); ?>; + +getBasePHPFHIRCopyrightComment(true); ?> + + +use PHPUnit\Framework\TestCase; + +class extends TestCase +{ + public function testNoErrorWithValidState() + { + $type = new ( + 'mock', + [ + 'stuff' => [ + 'class' => ::class, + 'collection' => true, + 'value' => ['value-1', 'value-2'], + ], + ], + ); + $rule = new (); + $err = $rule->assert($type, 'stuff', 2, $type->getStuff()); + $this->assertNull($err); + } + + public function testNoErrorWithEmptyValue() + { + $type = new ( + 'mock', + [ + 'stuff' => [ + 'class' => ::class, + 'collection' => true, + ], + ], + ); + $rule = new (); + $err = $rule->assert($type, 'stuff', 2, $type->getStuff()); + $this->assertNull($err); + } + + public function testErrorWithTooManyElements() + { + $type = new ( + 'mock', + [ + 'stuff' => [ + 'class' => ::class, + 'collection' => true, + 'value' => ['value-1', 'value-2', 'value-3'], + ], + ], + ); + $rule = new (); + $err = $rule->assert($type, 'stuff', 2, $type->getStuff()); + $this->assertNotNull($err); + } +} +getCoreFiles(); +$minOccursRule = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_RULE_CLASSNAME_MIN_OCCURS); + +$testCoreFiles = $config->getCoreTestFiles(); +$mockResource = $testCoreFiles->getCoreFileByEntityName(PHPFHIR_TEST_CLASSNAME_MOCK_RESOURCE_TYPE); +$mockPrimitive = $testCoreFiles->getCoreFileByEntityName(PHPFHIR_TEST_CLASSNAME_MOCK_STRING_PRIMITIVE_TYPE); + +$imports = $coreFile->getImports(); +$imports->addCoreFileImports( + $mockResource, + $mockPrimitive, + $minOccursRule, +); + +ob_start(); +echo " +namespace getFullyQualifiedNamespace(false); ?>; + +getBasePHPFHIRCopyrightComment(true); ?> + + +use PHPUnit\Framework\TestCase; + +class extends TestCase +{ + public function testNoErrorWithExact() + { + $type = new ( + 'mock', + [ + 'stuff' => [ + 'class' => ::class, + 'collection' => true, + 'value' => ['value-1', 'value-2'], + ], + ], + ); + $rule = new (); + $err = $rule->assert($type, 'stuff', 2, $type->getStuff()); + $this->assertNull($err); + } + + public function testNoErrorWithMore() + { + $type = new ( + 'mock', + [ + 'stuff' => [ + 'class' => ::class, + 'collection' => true, + 'value' => ['value-1', 'value-2', 'value-3'], + ], + ], + ); + $rule = new (); + $err = $rule->assert($type, 'stuff', 2, $type->getStuff()); + $this->assertNull($err); + } + + public function testErrorWithEmptyValue() + { + $type = new ( + 'mock', + [ + 'stuff' => [ + 'class' => ::class, + 'collection' => true, + ], + ], + ); + $rule = new (); + $err = $rule->assert($type, 'stuff', 2, $type->getStuff()); + $this->assertNotNull($err); + } + + public function testErrorWithLess() + { + $type = new ( + 'mock', + [ + 'stuff' => [ + 'class' => ::class, + 'collection' => true, + 'value' => ['value-1'], + ], + ], + ); + $rule = new (); + $err = $rule->assert($type, 'stuff', 2, $type->getStuff()); + $this->assertNotNull($err); + } +} +getCoreFiles(); +$maxLenRule = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_RULE_CLASSNAME_VALUE_MAX_LENGTH); + +$testCoreFiles = $config->getCoreTestFiles(); +$mockResource = $testCoreFiles->getCoreFileByEntityName(PHPFHIR_TEST_CLASSNAME_MOCK_RESOURCE_TYPE); +$mockPrimitive = $testCoreFiles->getCoreFileByEntityName(PHPFHIR_TEST_CLASSNAME_MOCK_STRING_PRIMITIVE_TYPE); + +$imports = $coreFile->getImports(); +$imports->addCoreFileImports( + $mockResource, + $mockPrimitive, + $maxLenRule, +); + +ob_start(); +echo " +namespace getFullyQualifiedNamespace(false); ?>; + +getBasePHPFHIRCopyrightComment(true); ?> + + +use PHPUnit\Framework\TestCase; + +class extends TestCase +{ + public function testNoErrorWithMax() + { + $type = new ('string-primitive', 'one'); + $rule = new (); + $err = $rule->assert($type, 'value', 3, $type->getValue()); + $this->assertNull($err); + } + + public function testNoErrorWithLess() + { + $type = new ('string-primitive', 'on'); + $rule = new (); + $err = $rule->assert($type, 'value', 3, $type->getValue()); + $this->assertNull($err); + } + + public function testNoErrorWithEmpty() + { + $type = new ('string-primitive', ''); + $rule = new (); + $err = $rule->assert($type, 'value', 3, $type->getValue()); + $this->assertNull($err); + } + + public function testErrorWithOverflow() + { + $type = new ('string-primitive', 'one '); + $rule = new (); + $err = $rule->assert($type, 'value', 3, $type->getValue()); + $this->assertNotNull($err); + } +} +getCoreFiles(); +$minLenRule = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_RULE_CLASSNAME_VALUE_MIN_LENGTH); + +$testCoreFiles = $config->getCoreTestFiles(); +$mockResource = $testCoreFiles->getCoreFileByEntityName(PHPFHIR_TEST_CLASSNAME_MOCK_RESOURCE_TYPE); +$mockPrimitive = $testCoreFiles->getCoreFileByEntityName(PHPFHIR_TEST_CLASSNAME_MOCK_STRING_PRIMITIVE_TYPE); + +$imports = $coreFile->getImports(); +$imports->addCoreFileImports( + $mockResource, + $mockPrimitive, + $minLenRule, +); + +ob_start(); +echo " +namespace getFullyQualifiedNamespace(false); ?>; + +getBasePHPFHIRCopyrightComment(true); ?> + + +use PHPUnit\Framework\TestCase; + +class extends TestCase +{ + public function testNoErrorWithMin() + { + $type = new ('string-primitive', 'one'); + $rule = new (); + $err = $rule->assert($type, 'value', 3, $type->getValue()); + $this->assertNull($err); + } + + public function testNoErrorWithMore() + { + $type = new ('string-primitive', 'one '); + $rule = new (); + $err = $rule->assert($type, 'value', 3, $type->getValue()); + $this->assertNull($err); + } + + public function testErrorWithLess() + { + $type = new ('string-primitive', 'on'); + $rule = new (); + $err = $rule->assert($type, 'value', 3, $type->getValue()); + $this->assertNotNull($err); + } + + public function testErrorWithEmpty() + { + $type = new ('string-primitive', ''); + $rule = new (); + $err = $rule->assert($type, 'value', 3, $type->getValue()); + $this->assertNotNull($err); + } +} +getCoreFiles(); +$oneOfRule = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_RULE_CLASSNAME_VALUE_ONE_OF); + +$testCoreFiles = $config->getCoreTestFiles(); +$mockResource = $testCoreFiles->getCoreFileByEntityName(PHPFHIR_TEST_CLASSNAME_MOCK_RESOURCE_TYPE); +$mockPrimitive = $testCoreFiles->getCoreFileByEntityName(PHPFHIR_TEST_CLASSNAME_MOCK_STRING_PRIMITIVE_TYPE); + +$imports = $coreFile->getImports(); +$imports->addCoreFileImports( + $mockResource, + $mockPrimitive, + $oneOfRule, +); + +ob_start(); +echo " +namespace getFullyQualifiedNamespace(false); ?>; + +getBasePHPFHIRCopyrightComment(true); ?> + + +use PHPUnit\Framework\TestCase; + +class extends TestCase +{ + public function testNoErrorWhenValueOnlyAllowed() + { + $type = new ('string-primitive', 'one'); + $rule = new (); + $err = $rule->assert($type, 'value', ['one'], $type->getValue()); + $this->assertNull($err); + } + + public function testNoErrorWhenValueAllowed() + { + $type = new ('string-primitive', 'one'); + $rule = new (); + $err = $rule->assert($type, 'value', ['one', 'two'], $type->getValue()); + $this->assertNull($err); + } + + public function testNoErrorWhemValueEmpty() + { + $type = new ('string-primitive', null); + $rule = new (); + $err = $rule->assert($type, 'value', ['one'], $type->getValue()); + $this->assertNull($err); + } + + public function testNoErrorWhenConstraintEmpty() + { + $type = new ('string-primitive', null); + $rule = new (); + $err = $rule->assert($type, 'value', [], $type->getValue()); + $this->assertNull($err); + } + + public function testErrWhenValueNotAllowed() + { + $type = new ('string-primitive', 'three'); + $rule = new (); + $err = $rule->assert($type, 'value', ['one', 'two'], $type->getValue()); + $this->assertNotNull($err); + } +} + namespace getFullyQualifiedNamespace(false); ?>; @@ -53,7 +52,7 @@ public function testNoErrorWithValidPatternAndValue() $type = new ('string-primitive', 'the quick brown fox jumped over the lazy dog'); $rule = new (); $err = $rule->assert($type, 'value', '/^[a-z\s]+$/', $type->getValue()); - $this->assertEquals('', $err); + $this->assertNull($err); } public function testErrorWithValidPatternAndInvalidValue() @@ -61,7 +60,15 @@ public function testErrorWithValidPatternAndInvalidValue() $type = new ('string-primitive', 'the quick brown fox jumped over the lazy dog'); $rule = new (); $err = $rule->assert($type, 'value', '/^[a-z]+$/', $type->getValue()); - $this->assertNotEmpty($err, 'Rule should have produced error'); + $this->assertNotNull($err); + } + + public function testErrorWithInvalidPattern() + { + $type = new ('string-primitive', 'the quick brown fox jumped over the lazy dog'); + $rule = new (); + $err = $rule->assert($type, 'value', '/^[a-+$/', $type->getValue()); + $this->assertNotNull($err); } /** @@ -73,7 +80,7 @@ public function testErrorWithValueOverflow() $type = new ('base64-primitive', $bigval); $rule = new (); $err = $rule->assert($type, 'value', '/^(\\s*([0-9a-zA-Z\\+\\/=]){4}\\s*)+$/', $type->getValue()); - $this->assertNotEmpty($err, 'Rule should have produced error'); + $this->assertNotNull($err); } } getCoreFiles(); +$imports = $coreFile->getImports(); +$testCoreFiles = $config->getCoreTestFiles(); + +$valueOneOfRule = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_RULE_CLASSNAME_VALUE_ONE_OF); +$minLenRule = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_RULE_CLASSNAME_VALUE_MIN_LENGTH); +$maxLenRule = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_RULE_CLASSNAME_VALUE_MAX_LENGTH); +$patternMatchRule = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_RULE_CLASSNAME_VALUE_PATTERN_MATCH); +$minOccursRule = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_RULE_CLASSNAME_MIN_OCCURS); +$maxOccursRule = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_RULE_CLASSNAME_MAX_OCCURS); + +$mockPrimitive = $testCoreFiles->getCoreFileByEntityName(PHPFHIR_TEST_CLASSNAME_MOCK_STRING_PRIMITIVE_TYPE); +$mockPrimitiveContainer = $testCoreFiles->getCoreFileByEntityName(PHPFHIR_TEST_CLASSNAME_MOCK_PRIMITIVE_CONTAINER_TPYE); +$mockElement = $testCoreFiles->getCoreFileByEntityName(PHPFHIR_TEST_CLASSNAME_MOCK_ELEMENT_TYPE); +$mockResource = $testCoreFiles->getCoreFileByEntityName(PHPFHIR_TEST_CLASSNAME_MOCK_RESOURCE_TYPE); + +$imports->addCoreFileImports( + $valueOneOfRule, + $minLenRule, + $maxLenRule, + $patternMatchRule, + $minOccursRule, + $maxOccursRule, + + $mockPrimitive, + $mockPrimitiveContainer, + $mockElement, + $mockResource, +); + +ob_start(); +echo " +namespace getFullyQualifiedNamespace(false); ?>; + +getBasePHPFHIRCopyrightComment(true); ?> + + +use PHPUnit\Framework\TestCase; + +class extends TestCase +{ + public function testCanSetValidationRules() + { + $valueRules = [::NAME => '/^[a-z-]+$/']; + $type = new (); + $type->_setFieldValidationRules('value', $valueRules); + $rules = $type->_getCombinedValidationRules(); + $this->assertCount(1, $rules, var_export($rules, true)); + $this->assertArrayHasKey('value', $rules); + $this->assertEquals($valueRules, $rules['value'] ?? []); + $this->assertArrayHasKey(::NAME, $rules['value'] ?? []); + } + + function testCanValidateSimpleTypeNoErrors() + { + $type = new (value: 'ye-p', validationRuleMap: ['value' => [::NAME => '/^[a-z-]+$/']]); + $errs = $type->_getValidationErrors(); + $this->assertCount(0, $errs, var_export($errs, true)); + } + + function testCanValidateSimpleTypeWithErrors() + { + $type = new (value: 'NOPE.', validationRuleMap: ['value' => [::NAME => '/^[a-z-]+$/']]); + $errs = $type->_getValidationErrors(); + $this->assertCount(1, $errs, var_export($errs, true)); + } + + public function testCanValidateComplexTypeNoErrors() + { + $type = new ( + name: 'mock', + fields: [ + 'identifier' => [ + 'class' => ::class, + 'value' => new ( + name: 'string', + fields: [ + 'value' => [ + 'class' => ::class, + 'value' => new ( + value: 'mock-1', + validationRuleMap: ['value' => [::NAME => '/^[a-z0-9-]+$/']], + ), + ], + ], + ), + ], + ], + ); + $this->assertCount(1, $type->getIdentifier()->getValue()->_getCombinedValidationRules()); + $errs = $type->_getValidationErrors(); + $this->assertEmpty($errs, var_export($errs, true)); + } + + public function testCanValidateComplexTypeWithErrors() + { + $type = new ( + name: 'mock', + fields: [ + 'identifier' => [ + 'class' => ::class, + 'value' => new ( + name: 'string', + fields: [ + 'value' => [ + 'class' => ::class, + 'value' => new ( + value: 'mock_1', + validationRuleMap: ['value' => [::NAME => '/^[a-z-]+$/']], + ), + ], + ], + ), + ], + ], + ); + $this->assertCount(1, $type->getIdentifier()->getValue()->_getCombinedValidationRules()); + $errs = $type->_getValidationErrors(); + $this->assertCount(1, $errs, var_export($errs, true)); + } + + public function testCanValidateSimpleCollectionFieldNoErrors() + { + $type = new ( + name: 'mock', + fields: [ + 'code' => [ + 'class' => ::class, + 'collection' => true, + 'value' => [ + new ( + value: 'mock-1', + validationRuleMap: ['value' => [::NAME => '/^[a-z0-9-]+$/']], + ), + new ( + value: 'mock-2', + validationRuleMap: ['value' => [::NAME => '/^[a-z0-9-]+$/']], + ), + ], + ], + ], + validationRuleMap: [ + 'code' => [ + ::NAME => 2, + ], + ], + ); + $errs = $type->_getValidationErrors(); + $this->assertCount(0, $errs, var_export($errs, true)); + } + + public function testCanValidateSimpleCollectionFieldErrors() + { + $type = new ( + name: 'mock', + fields: [ + 'code' => [ + 'class' => ::class, + 'collection' => true, + 'value' => [ + new ( + value: 'mock-1', + validationRuleMap: ['value' => [::NAME => '/^[a-z0-9-]+$/']], + ), + new ( + value: 'mock-2', + validationRuleMap: ['value' => [::NAME => '/^[a-z-]+$/']], + ), + ], + ], + ], + validationRuleMap: [ + 'code' => [ + ::NAME => 3, + ], + ], + ); + $errs = $type->_getValidationErrors(); + $this->assertCount(2, $errs, var_export($errs, true)); + } +} +getCoreFiles(); +$imports = $coreFile->getImports(); +$testCoreFiles = $config->getCoreTestFiles(); + +$validatorClass = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_CLASSNAME_VALIDATOR); +$valueOneOfRule = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_RULE_CLASSNAME_VALUE_ONE_OF); +$minLenRule = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_RULE_CLASSNAME_VALUE_MIN_LENGTH); +$maxLenRule = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_RULE_CLASSNAME_VALUE_MAX_LENGTH); +$patternMatchRule = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_RULE_CLASSNAME_VALUE_PATTERN_MATCH); +$minOccursRule = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_RULE_CLASSNAME_MIN_OCCURS); +$maxOccursRule = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_RULE_CLASSNAME_MAX_OCCURS); + +$imports->addCoreFileImports( + $validatorClass, + + $valueOneOfRule, + $minLenRule, + $maxLenRule, + $patternMatchRule, + $minOccursRule, + $maxOccursRule, +); + +ob_start(); +echo " +namespace getFullyQualifiedNamespace(false); ?>; + +getBasePHPFHIRCopyrightComment(true); ?> + + +use PHPUnit\Framework\TestCase; + +class extends TestCase +{ + public function testValidatorHasBaseRules() + { + $rules = ::getRules(); + $this->assertCount(6, $rules); + + $this->assertArrayHasKey(::NAME, $rules); + $this->assertInstanceOf(::class, $rules[::NAME]); + + $this->assertArrayHasKey(::NAME, $rules); + $this->assertInstanceOf(::class, $rules[::NAME]); + + $this->assertArrayHasKey(::NAME, $rules); + $this->assertInstanceOf(::class, $rules[::NAME]); + + $this->assertArrayHasKey(::NAME, $rules); + $this->assertInstanceOf(::class, $rules[::NAME]); + + $this->assertArrayHasKey(::NAME, $rules); + $this->assertInstanceOf(::class, $rules[::NAME]); + + $this->assertArrayHasKey(::NAME, $rules); + $this->assertInstanceOf(::class, $rules[::NAME]); + } +} +getDefinition()->getTypes(); +$versionCoreFiles = $version->getCoreFiles(); +$imports = $coreFile->getImports(); + +$typeMapClass = $versionCoreFiles->getCoreFileByEntityName(PHPFHIR_VERSION_CLASSNAME_VERSION_TYPE_MAP); + +$imports->addCoreFileImports( + $typeMapClass, +); -$coreFiles = $version->getCoreFiles(); +$types = $version->getDefinition()->getTypes(); ob_start(); -echo " +echo " namespace getFullyQualifiedNamespace(false); ?>; getSourceMetadata()->getFullPHPFHIRCopyrightComment(); ?> -use getCoreFileByEntityName(PHPFHIR_VERSION_CLASSNAME_VERSION_TYPE_MAP) - ->getFullyQualifiedName(false); ?>; + use PHPUnit\Framework\TestCase; -class extends TestCase +class extends TestCase { - public function testGetTypeClassWithNonStringReturnsNull() + public function testGetTypeClassnameWithInvalidString() { - $this->assertNull(::getTypeClassName(1)); + $this->assertNull(::getTypeClassname('\\stdClass')); } - public function testGetTypeClassName() + public function testGetTypeClassnameWithInvalidXML() { -getNamespaceSortedIterator() as $type): ?> - $this->assertEquals('getFullyQualifiedClassName(true); ?>', ::getTypeClassName('getFHIRName(); ?>')); + $sxe = new \SimpleXMLElement(''); + $this->assertNull(::getTypeClassname($sxe)); + } + + public function testGetTypeClassnameWithJSONMissingResourceType() + { + $json = new \stdClass(); + $json->jimmy = 'Observation'; + $this->assertNull(::getTypeClassname($json)); + } + + public function testGetTypeClassnameWithJSONInvalidResourceType() + { + $json = new \stdClass(); + $json-> = 'Steve'; + $this->assertNull(::getTypeClassname($json)); + } + + public function testGetTypeClassnameWithTypeName() + { +getNamespaceSortedIterator() as $type) : ?> + $this->assertEquals('getFullyQualifiedClassName(true); ?>', ::getTypeClassname('getFHIRName(); ?>')); } - public function testGetContainedTypeClassName() + public function testGetContainedTypeClassnameWithTypeName() { -getNameSortedIterator() as $type) : +getNamespaceSortedIterator() as $type) : if ($type->isContainedType()) : ?> - $this->assertEquals('getFullyQualifiedClassName(true); ?>', ::getContainedTypeClassName('getFHIRName(); ?>')); + $this->assertEquals('getFullyQualifiedClassName(true); ?>', ::getContainedTypeClassname('getFHIRName(); ?>')); - $this->assertNull(::getContainedTypeClassName('getFHIRName(); ?>')); + $this->assertNull(::getContainedTypeClassname('getFHIRName(); ?>')); } public function testIsContainableResourceWithClassname() { -getNameSortedIterator() as $type) : - // TODO(@dcarbone): don't do this. - if ($type->getFHIRName() === PHPFHIR_XHTML_TYPE_NAME) { - continue; - } +getNamespaceSortedIterator() as $type) : if ($type->isContainedType()) : ?> - $this->assertTrue(::isContainableResource('getFullyQualifiedClassName(false); ?>')); - $this->assertTrue(::isContainableResource('getFullyQualifiedClassName(true); ?>')); + $this->assertTrue(::isContainableType('getFullyQualifiedClassName(false); ?>'), sprintf('Expected input "%s" to return true.', 'getFullyQualifiedClassName(false); ?>')); + $this->assertTrue(::isContainableType('getFullyQualifiedClassName(true); ?>'), sprintf('Expected input "%s" to return true.', 'getFullyQualifiedClassName(true); ?>')); - $this->assertFalse(::isContainableResource('getFullyQualifiedClassName(false); ?>')); - $this->assertFalse(::isContainableResource('getFullyQualifiedClassName(true); ?>')); + $this->assertFalse(::isContainableType('getFullyQualifiedClassName(false); ?>'), sprintf('Expected input "%s" to return false.', 'getFullyQualifiedClassName(false); ?>')); + $this->assertFalse(::isContainableType('getFullyQualifiedClassName(true); ?>'), sprintf('Expected input "%s" to return false.', 'getFullyQualifiedClassName(true); ?>')); } - public function testIsContainableResourceWithTypeName() + public function testIsContainableResourceWithInstance() { -getNameSortedIterator() as $type) : +getNamespaceSortedIterator() as $type) : if ($type->isAbstract()) { continue; - } - // TODO(@dcarbone): don't do this. - if ($type->getFHIRName() === PHPFHIR_XHTML_TYPE_NAME) { - continue; - } - if ($type->isContainedType()) : ?> - $this->assertTrue(::isContainableResource('getFHIRName(); ?>')); + } ?> + $type = new getFullyQualifiedClassName(true); ?>; +isContainedType()) : ?> + $this->assertTrue(::isContainableType($type), sprintf('Expected instance of "%s" to return true.', $type::class)); + $this->assertTrue(::isContainableType($type->_getFHIRTypeName()), sprintf('Expected input "%s" to return true.', $type->_getFHIRTypeName())); - $this->assertFalse(::isContainableResource('getFHIRName(); ?>')); + $this->assertFalse(::isContainableType($type), sprintf('Expected instance of "%s" to return false.', $type::class)); + $this->assertFalse(::isContainableType($type->_getFHIRTypeName()), sprintf('Expected input "%s" to return false.', $type->_getFHIRTypeName())); } - public function testIsContainableResourceWithInstance() + public function testIsContainableTypeWithXML() { getNameSortedIterator() as $type) : if ($type->isAbstract()) { continue; + } ?> + $sxe = new \SimpleXMLElement('<getFHIRName(); ?>>getFHIRName(); ?>>'); +isContainedType()) : ?> + $this->assertTrue(::isContainableType($sxe), sprintf('Expected input "%s" to return true.', $sxe->saveXML())); + + $this->assertFalse(::isContainableType($sxe), sprintf('Expected input "%s" to return false.', $sxe->saveXML())); + } - // TODO(@dcarbone): don't do this. - if ($type->getFHIRName() === PHPFHIR_XHTML_TYPE_NAME) { + + public function testIsContainableTypeWithJSON() + { +getNameSortedIterator() as $type) : + if ($type->isAbstract()) { continue; - } -?> - $type = new getFullyQualifiedClassName(true); ?>; + } ?> + $json = new \stdClass(); + $json-> = 'getFHIRName(); ?>'; isContainedType()) : ?> - $this->assertTrue(::isContainableResource($type)); + $this->assertTrue(::isContainableType($json), sprintf('Expected input "%s" to return true.', var_export($json, true))); - $this->assertFalse(::isContainableResource($type)); + $this->assertFalse(::isContainableType($json), sprintf('Expected input "%s" to return false.', var_export($json, true))); } diff --git a/template/tests/versions/types/class.php b/template/tests/versions/types/class.php index 4b1aedfc..fb75bf4f 100644 --- a/template/tests/versions/types/class.php +++ b/template/tests/versions/types/class.php @@ -18,7 +18,6 @@ use DCarbone\PHPFHIR\Builder\Imports; use DCarbone\PHPFHIR\Enum\PrimitiveTypeEnum; -use DCarbone\PHPFHIR\Enum\TypeKindEnum; use DCarbone\PHPFHIR\Utilities\ImportUtils; /** @var \DCarbone\PHPFHIR\Version $version */ @@ -44,7 +43,10 @@ $imports = new Imports($version->getConfig(), $type->getFullyQualifiedTestNamespace(false), $type->getTestClassName()); $imports->addVersionTypeImports($type); -if ($type->isResourceType()) { +if (!$type->isAbstract() + && $type !== $bundleType + && !$type->getKind()->isResourceContainer($version) + && ($type->isResourceType() || $type->hasResourceTypeParent())) { $imports ->addCoreFileImportsByName( PHPFHIR_CLIENT_CLASSNAME_CONFIG, @@ -70,7 +72,8 @@ ob_start(); -echo ' +echo ' + namespace getFullyQualifiedTestNamespace(false); ?>; @@ -81,8 +84,12 @@ use PHPUnit\Framework\TestCase; class getTestClassName(); ?> extends TestCase -{isResourceType()) : ?> - +{ +isAbstract() + && $type !== $bundleType + && !$type->getKind()->isResourceContainer($version) + && ($type->isResourceType() || $type->hasResourceTypeParent())) : ?> protected $_version; protected function setUp(): void @@ -111,15 +118,19 @@ protected function _getClient(): $this->_version, ); } - + public function testCanConstructTypeNoArgs() { $type = new getClassName(); ?>(); - $this->assertInstanceOf('getFullyQualifiedClassName(true); ?>', $type); + $this->assertEquals('getFHIRName(); ?>', $type->_getFHIRTypeName()); } -getPrimitiveType(); +isPrimitiveType() || $type->hasPrimitiveTypeParent() || $type->isPrimitiveContainer() || $type->hasPrimitiveContainerParent()) : + $primitiveType = match(true) { + ($type->isPrimitiveType() || $type->hasPrimitiveTypeParent()) => $type->getPrimitiveType(), + $type->isPrimitiveContainer() => $type->getProperties()->getProperty('value')->getValueFHIRType()->getPrimitiveType(), + $type->hasPrimitiveContainerParent() => $type->getParentProperty('value')->getValueFHIRType()->getPrimitiveType(), + }; // TODO: more different types of strvals... $strVals = match ($primitiveType) { @@ -131,24 +142,48 @@ public function testCanConstructTypeNoArgs() default => ['randomstring'], }; ?> - public function testCanConstructWithString() { - $n = new getClassName(); ?>(''); - $this->assertEquals('', (string)$n); + $type = new getClassName(); ?>(value: ''); + $this->assertEquals('', $type->_getValueAsString()); + $this->assertEquals('', (string)$type); } public function testCanSetValueFromString() { - $n = new getClassName(); ?>; - $n->setValue(''); - $this->assertEquals('', (string)$n); + $type = new getClassName(); ?>(); + $type->setValue(''); + $this->assertEquals('', $type->_getValueAsString()); + $this->assertEquals('', (string)$type); } -getSourceMetadata()->isDSTU1() && $type->isResourceType() && !$type->getKind()->isResourceContainer($version)) : ?> +getSourceMetadata()->isDSTU1()) : + if ($type->isResourceType() || $type->hasResourceTypeParent()) : ?> + + public function testGetFHIRVersionName() + { + $type = new getFullyQualifiedClassName(true); ?>; + $this->assertEquals(getFullyQualifiedName(true); ?>::NAME, $type->_getFHIRVersionName()); + } + + public function testGetFHIRSemanticVersion() + { + $type = new getFullyQualifiedClassName(true); ?>; + $this->assertEquals(getFullyQualifiedName(true); ?>::FHIR_SEMANTIC_VERSION, $type->_getFHIRSemanticVersion()); + } + + public function testGetFHIRShortVersion() + { + $type = new getFullyQualifiedClassName(true); ?>; + $this->assertEquals(getFullyQualifiedName(true); ?>::FHIR_SHORT_VERSION, $type->_getFHIRShortVersion()); + } +isAbstract() + && $type !== $bundleType + && !$type->getKind()->isResourceContainer($version) + && ($type->isResourceType() || $type->hasResourceTypeParent())) : ?> public function testCanTranscodeBundleJSON() { @@ -213,6 +248,15 @@ public function testCanTranscodeBundleXML() $xw = $bundle->xmlSerialize(config: $this->_version->getConfig()->getSerializeConfig()); $this->assertXmlStringEqualsXmlString($rc->getResp(), $xw->outputMemory()); } - -} + + + public function testCanExecuteValidations() + { + $type = new getclassName(); ?>(); + $errs = $type->_getValidationErrors(); + $this->assertIsArray($errs); + } +} ')); if ('' !== $dlDir) { $this->assertDirectoryExists($dlDir, sprintf('Configured test resource download directory "%s" does not exist.', $dlDir)); - $fname = sprintf('%s%sgetFHIRName(); ?>-getSourceMetadata()->getFHIRVersionString(false); ?>-source.%s', $dlDir, DIRECTORY_SEPARATOR, $format); + $fname = sprintf('%s%sgetFHIRName(); ?>-getSourceMetadata()->getSemanticVersion(false); ?>-source.%s', $dlDir, DIRECTORY_SEPARATOR, $format); file_put_contents($fname, $rc->resp); $this->assertFileExists($fname, sprintf('Failed to write fetched resource bundle to "%s"', $fname)); } diff --git a/template/versions/core/class_version.php b/template/versions/core/class_version.php index 0534b17e..c12e92f6 100644 --- a/template/versions/core/class_version.php +++ b/template/versions/core/class_version.php @@ -44,6 +44,8 @@ $versionTypeMapClass, ); +$sourceMeta = $version->getSourceMetadata(); + ob_start(); echo 'declare(strict_types=1); @@ -51,14 +53,16 @@ getSourceMetadata()->getFullPHPFHIRCopyrightComment(); ?> + class implements { public const NAME = 'getName(); ?>'; - public const SOURCE_VERSION = 'getSourceMetadata()->getFHIRVersionString(false); ?>'; - public const SOURCE_GENERATION_DATE = 'getSourceMetadata()->getFHIRGenerationDate(); ?>'; + public const FHIR_SEMANTIC_VERSION = 'getSemanticVersion(false); ?>'; + public const FHIR_SHORT_VERSION = 'getShortVersion(); ?>'; + public const FHIR_GENERATION_DATE = 'getSourceGenerationDate(); ?>'; private const _GENERATED_CONFIG = getDefaultConfig()->toArray(), 1); ?>; @@ -97,17 +101,25 @@ public function getName(): string /** * @return string */ - public function getSourceVersion(): string + public function getFHIRSemanticVersion(): string + { + return self::FHIR_SEMANTIC_VERSION; + } + + /** + * @return string + */ + public function getFHIRShortVersion(): string { - return self::SOURCE_VERSION; + return self::FHIR_SHORT_VERSION; } /** * @return string */ - public function getSourceGenerationDate(): string + public function getFHIRGenerationDate(): string { - return self::SOURCE_GENERATION_DATE; + return self::FHIR_GENERATION_DATE; } /** diff --git a/template/versions/core/class_version_type_map.php b/template/versions/core/class_version_type_map.php index f791ce26..45c4d227 100644 --- a/template/versions/core/class_version_type_map.php +++ b/template/versions/core/class_version_type_map.php @@ -22,18 +22,26 @@ /** @var \DCarbone\PHPFHIR\Version $version */ /** @var \DCarbone\PHPFHIR\CoreFile $coreFile */ -$imports = $coreFile->getImports(); - -$imports->addCoreFileImportsByName( - PHPFHIR_CLASSNAME_CONSTANTS, - PHPFHIR_TYPES_INTERFACE_TYPE, - PHPFHIR_INTERFACE_VERSION_TYPE_MAP, -); - $config = $version->getConfig(); $coreFiles = $config->getCoreFiles(); +$versionCoreFiles = $version->getCoreFiles(); +$imports = $coreFile->getImports(); +$constantsClass = $coreFiles->getCoreFileByEntityName(PHPFHIR_CLASSNAME_CONSTANTS); $typeInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_TYPE); +$typeMapInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_INTERFACE_VERSION_TYPE_MAP); + +$versionConstants = $versionCoreFiles->getCoreFileByEntityName(PHPFHIR_VERSION_CLASSNAME_VERSION_CONSTANTS); +$versionContainedTypeInterface = $versionCoreFiles->getCoreFileByEntityName(PHPFHIR_VERSION_INTERFACE_VERSION_CONTAINED_TYPE); + +$imports->addCoreFileImports( + $constantsClass, + $typeInterface, + $typeMapInterface, + + $versionConstants, + $versionContainedTypeInterface, +); $types = $version->getDefinition()->getTypes(); @@ -66,7 +74,7 @@ -class implements +class implements { private const _TYPE_MAP = [ @@ -79,16 +87,6 @@ class implements getTypeNameConst(true); ?> => getClassNameConst(true); ?>, ]; - /** - * Get fully qualified class name for FHIR Type name. Returns null if type not found - * @param string $typeName - * @return string|null - */ - public static function getTypeClassName(string $typeName): null|string - { - return self::_TYPE_MAP[$typeName] ?? null; - } - /** * Returns the full internal class map * @return array @@ -99,7 +97,8 @@ public static function getMap(): array } /** - * Returns the full list of containable resource types + * Returns a map of [ "typeName" => "typeClass" ] of all types that may be contained within a resource container. + * * @return array */ public static function getContainableTypes(): array @@ -108,72 +107,91 @@ public static function getContainableTypes(): array } /** - * @param string $typeName Name of FHIR object reference by getFHIRName(); ?> + * Returns the fully qualified classname for the provided input, if it represents a FHIR type. + * + * @param string|\stdClass|\SimpleXMLElement $input Expects either name of type or unserialized JSON or XML. + * @return string|null + */ + public static function getTypeClassname(string|\stdClass|\SimpleXMLElement $input): null|string + { + if (is_string($input)) { + return self::_TYPE_MAP[$input] ?? null; + } else if ($input instanceof \SimpleXMLElement) { + return self::_TYPE_MAP[$input->getName()] ?? null; + } else if (isset($input->)) { + return self::_TYPE_MAP[$input->] ?? null; + } else { + return null; + } + } + /** + * Attempts to return the fully qualified classname of a contained type from the provided input, if it + * represents one. + * + * @param string|\stdClass|\SimpleXMLElement $input Expects either name of type or unserialized JSON or XML. * @return string|null Name of class as string or null if type is not contained in map */ - public static function getContainedTypeClassName(string $typeName): null|string + public static function getContainedTypeClassname(string|\stdClass|\SimpleXMLElement $input): null|string { - return self::_CONTAINABLE_TYPES[$typeName] ?? null; + $classname = self::getTypeClassname($input); + if (null !== $classname && in_array($classname, self::_CONTAINABLE_TYPES, true)) { + return $classname; + } + return null; } /** - * Will attempt to determine if the provided value is or describes a containable resource type - * @param string|array|\SimpleXMLElement|getFullyQualifiedName(true); ?> $type + * Attempts to determine if the provided value is or represents a containable resource type + * + * @param string|\stdClass|\SimpleXMLElement|getFullyQualifiedName(true); ?> $input * @return bool * @throws \InvalidArgumentException */ - public static function isContainableResource(string|array|\SimpleXMLElement| $type): bool + public static function isContainableType(string|\stdClass|\SimpleXMLElement| $input): bool { - $tt = gettype($type); - if ('object' === $tt) { - if ($type instanceof ) { - return ($type instanceof ); - } - return isset(self::_CONTAINABLE_TYPES[$type->getName()]); - } - if ('string' === $tt) { - return isset(self::_CONTAINABLE_TYPES[$type]) || in_array('\\' . ltrim($type, '\\'), self::_CONTAINABLE_TYPES, true); - } - if (isset($type[::JSON_FIELD_RESOURCE_TYPE])) { - return isset(self::_CONTAINABLE_TYPES[$type[::JSON_FIELD_RESOURCE_TYPE]]); + if ($input instanceof ) { + return ($input instanceof ); + } else if (is_string($input) && str_contains($input, '\\')) { + return isset(self::_CONTAINABLE_TYPES[$input]) || in_array('\\' . ltrim($input, '\\'), self::_CONTAINABLE_TYPES, true); + } else { + return null !== self::getContainedTypeClassname($input); } - return false; } /** * @param \SimpleXMLElement $node Parent element containing inline resource * @return string Fully qualified class name of contained resource type + * @throws \UnexpectedValueException */ - public static function getContainedTypeClassNameFromXML(\SimpleXMLElement $node): string + public static function mustGetContainedTypeClassnameFromXML(\SimpleXMLElement $node): string { $typeName = $node->getName(); - $className = self::getContainedTypeClassName($typeName); - if (null === $className) { - throw self::createdInvalidContainedTypeException($typeName); + if (isset(self::_CONTAINABLE_TYPES[$typeName])) { + return self::_CONTAINABLE_TYPES[$typeName]; } - return $className; + throw self::createdInvalidContainedTypeException($typeName); } /** * @param \stdClass $json * @return string Fully qualified class name of contained resource type + * @throws \UnexpectedValueException + * @throws \DomainException */ - public static function getContainedTypeClassNameFromJSON(\stdClass $json): string + public static function mustGetContainedTypeClassnameFromJSON(\stdClass $json): string { - $resourceType = $json-> ?? null; - if (null === $resourceType) { + if (!isset($json->)) { throw new \DomainException(sprintf( 'Unable to determine contained Resource type from input (missing "%s" key). Keys: ["%s"]', - ::JSON_FIELD_RESOURCE_TYPE, + ::JSON_FIELD_RESOURCE_TYPE, implode('","', array_keys((array)$json)) )); } - $className = self::getContainedTypeClassName($resourceType); - if (null === $className) { - throw self::createdInvalidContainedTypeException($resourceType); + if (isset(self::_CONTAINABLE_TYPES[$json->])) { + return self::_CONTAINABLE_TYPES[$json->]; } - return $className; + throw self::createdInvalidContainedTypeException($json->); } /** diff --git a/template/versions/types/class_default.php b/template/versions/types/class_default.php index 2f1fafe2..298a23a5 100644 --- a/template/versions/types/class_default.php +++ b/template/versions/types/class_default.php @@ -27,10 +27,15 @@ throw new \LogicException(sprintf('Cannot use template %s for Type "%s"', __FILE__, $type->getFHIRName())); } +$sourceMeta = $version->getSourceMetadata(); + $coreFiles = $version->getConfig()->getCoreFiles(); +$versionCoreFiles = $version->getCoreFiles(); $xmlLocationEnum = $coreFiles->getCoreFileByEntityName(PHPFHIR_ENCODING_ENUM_VALUE_XML_LOCATION); +$versionClass = $versionCoreFiles->getCoreFileByEntityName(PHPFHIR_VERSION_CLASSNAME_VERSION); + // define some common things $typeKind = $type->getKind(); @@ -68,27 +73,20 @@ /* */ // The default validation rules for this type as defined in the FHIR schema used to generate this code. - private const _FHIR_VALIDATION_RULES = [hasNonOverloadedProperties() || !$type->hasPropertiesWithValidations()): ?>]; -getProperties()->getIterator() as $property) : - if ($property->getOverloadedProperty()) { - continue; - } + private const _FHIR_VALIDATION_RULES = [ +getAllPropertiesIndexedIterator() as $property) : $validationMap = $property->buildValidationMap($type); if ([] !== $validationMap) : ?> - self::getFieldConstantName(); ?> => [ $v) : ?> ::NAME => , - ], - ]; hasPrimitiveTypeParent() && $type->hasNonOverloadedProperties()) : @@ -144,21 +142,33 @@ endif; ?> /* */ - /** - * @return string - */ public function _getFHIRTypeName(): string { return self::FHIR_TYPE_NAME; } -hasConcreteParent() && ($sourceMeta->isDSTU1() || $type->isResourceType())) : ?> + + /* */ + public function _getFHIRVersionName(): string + { + return ::NAME; + } + public function _getFHIRSemanticVersion(): string + { + return ::FHIR_SEMANTIC_VERSION; + } + + public function _getFHIRShortVersion(): string + { + return ::FHIR_SHORT_VERSION; + } +isContainedType()) : ?> /* */ - /** - * @return string - */ public function _getResourceType(): string { return static::FHIR_TYPE_NAME; @@ -220,12 +230,9 @@ public function _nonValueFieldDefined(): bool endif; endif; -if (null === $type->getParentType() && !$type->hasPrimitiveTypeParent()) : ?> +if (null === $type->getParentType() || ($type->isPrimitiveContainer() && !$type->hasPrimitiveContainerParent())) : ?> /* */ - /** - * @return string - */ public function __toString(): string { getConfig()->getCoreFiles(); - // define some common things $typeKind = $type->getKind(); $typeClassName = $type->getClassName(); @@ -56,15 +54,14 @@ ] ); ?> public const getFieldConstantName(); ?> = 'getName(); ?>'; - + public const TRUE = 'true'; public const FALSE = 'false'; /* */ - // The default validation rules for this type as defined in the FHIR schema used to generate this code. - private const _FHIR_VALIDATION_RULES = [hasNonOverloadedProperties() || !$type->hasPropertiesWithValidations()): ?>]; + private const _FHIR_VALIDATION_RULES = []; self::getFieldConstantName(); ?> => [ diff --git a/template/versions/types/class_resource_container.php b/template/versions/types/class_resource_container.php index e02e8c6a..1e4576f8 100644 --- a/template/versions/types/class_resource_container.php +++ b/template/versions/types/class_resource_container.php @@ -16,179 +16,111 @@ * limitations under the License. */ +use DCarbone\PHPFHIR\Utilities\ImportUtils; + /** @var \DCarbone\PHPFHIR\Version $version */ /** @var \DCarbone\PHPFHIR\Version\Definition\Type $type */ -use DCarbone\PHPFHIR\Utilities\NameUtils; - $config = $version->getConfig(); $coreFiles = $config->getCoreFiles(); - -$xmlWriterClass = $coreFiles->getCoreFileByEntityName(PHPFHIR_ENCODING_CLASSNAME_XML_WRITER); -$serializeConfigClass = $coreFiles->getCoreFileByEntityName(PHPFHIR_ENCODING_CLASSNAME_SERIALIZE_CONFIG); -$unserializeConfigClass = $coreFiles->getCoreFileByEntityName(PHPFHIR_ENCODING_CLASSNAME_UNSERIALIZE_CONFIG); - $versionCoreFiles = $version->getCoreFiles(); +$imports = $type->getImports(); +$resourceContainerInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_RESOURCE_CONTAINER_TYPE); +$containedTypeInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_CONTAINED_TYPE); +$typeValidationTrait = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_TRAIT_TYPE_VALIDATIONS); + +$versionClass = $versionCoreFiles->getCoreFileByEntityName(PHPFHIR_VERSION_CLASSNAME_VERSION); +$versionConstants = $versionCoreFiles->getCoreFileByEntityName(PHPFHIR_VERSION_CLASSNAME_VERSION_CONSTANTS); $versionContainedTypeInterface = $versionCoreFiles->getCoreFileByEntityName(PHPFHIR_VERSION_INTERFACE_VERSION_CONTAINED_TYPE); -$versionTypeMapClass = $versionCoreFiles->getCoreFileByEntityName(PHPFHIR_VERSION_CLASSNAME_VERSION_TYPE_MAP); + +$imports->addCoreFileImports( + $resourceContainerInterface, + $containedTypeInterface, + $typeValidationTrait, + + $versionClass, + $versionConstants, + $versionContainedTypeInterface, +); ob_start(); +echo 'declare(strict_types=1); -// build file header -echo require_with( - PHPFHIR_TEMPLATE_VERSION_TYPES_DIR . '/header.php', - [ - 'version' => $version, - 'type' => $type, - ] -); ?> + namespace getFullyQualifiedNamespace(false); ?>; + +getBasePHPFHIRCopyrightComment(true); ?> + + + +class getClassName(); ?> implements + +{ + use ; + + public const FHIR_TYPE_NAME = getTypeNameConst(true); ?>; + + private const _FHIR_VALIDATION_RULES = []; /** @var null|getFullyQualifiedName(true); ?> */ private null| $containedType = null; - public function __construct(null| $containedType = null, - null|iterable $fhirComments = null) + public function __construct(null| $containedType = null) { if (null !== $containedType) { $this->setContainedType($containedType); } - if (null !== $fhirComments) { - $this->_setFHIRComments($fhirComments); - } } - public function _getFHIRTypeName(): string { return self::FHIR_TYPE_NAME; } - /** - * TODO: empty, pending validation system overhaul - */ - public function _getValidationRules(): array - { - return []; - } - - /** - * TODO: empty, pending validation system overhaul - */ - public function _getValidationErrors(): array - { - return []; - } - /** * @return null|getFullyQualifiedName(true); ?> */ - public function getContainedType(): null| + public function getContainedType(): null| { - return $this->containedType ?? null; + return $this->contained ?? null; } /** * @param null|getFullyQualifiedName(true); ?> $containedType * @return static */ - public function setContainedType(null| $containedType): self + public function setContainedType(null| $containedType): self { if (null === $containedType) { unset($this->containedType); return $this; } + if (!($containedType instanceof )) { + throw new \InvalidArgumentException(sprintf( + 'Contained type must implement "%s", provided type "%s" does not.', + ::class, + $containedType::class, + )); + } $this->containedType = $containedType; return $this; } - $version, - 'type' => $type, - ] -); -?> - foreach ($element->children() as $child) { - /** @var getFullyQualifiedName(true); ?> $class */ - $class = ::getContainedTypeClassNameFromXML($child); - $type->setContainedType($class::xmlUnserialize($child, $config)); - break; - } - return $type; - } - - /** - * @param null|getFullyQualifiedName(true); ?> $xw - * @param null|getFullyQualifiedName(true); ?> $config - * @return getFullyQualifiedName(true); ?> - - */ - public function xmlSerialize(null| $xw = null, - null| $config = null): - - { - $containedType = $this->getContainedType(); - if (null !== $containedType) { - return $containedType->xmlSerialize($xw, $config); - } - if (null === $config) { - $config = (new ())->getConfig()->getSerializeConfig(); - } - if (null === $xw) { - $xw = new ($config); - } - if (!$xw->isOpen()) { - $xw->openMemory(); - } - if (!$xw->isDocStarted()) { - $docStarted = true; - $xw->startDocument(); - } - if (!$xw->isRootOpen()) { - $rootOpened = true; - $xw->openRootNode('', $this->_getSourceXMLNS()); - } - if (isset($rootOpened) && $rootOpened) { - $xw->endElement(); - } - if (isset($docStarted) && $docStarted) { - $xw->endDocument(); - } - return $xw; - } - - $version, - 'type' => $type, - ] -); -?> - /** @var getFullyQualifiedName(true); ?> $class */ - $class = ::getContainedTypeClassNameFromJSON($json); - $type->setContainedType($class::jsonUnserialize($json)); - return $type; - } - public function __toString(): string { - return self::FHIR_TYPE_NAME; + return (string)($this->containedType ?? self::FHIR_TYPE_NAME); } /** - * @return null|object + * @return null|getFullyQualifiedName(true); ?> + */ - public function jsonSerialize(): mixed + public function jsonSerialize(): null| + { - return $this->getContainedType(); + return $this->containedType ?? null; } } getConfig(); $coreFiles = $config->getCoreFiles(); +$imports = $type->getimports(); $primitiveTypeInterface = $coreFiles->getCoreFileByEntityName(PHPFHIR_TYPES_INTERFACE_TYPE); $typeValidationTrait = $coreFiles->getCoreFileByEntityName(PHPFHIR_VALIDATION_TRAIT_TYPE_VALIDATIONS); -$imports = $type->getimports(); $imports->addCoreFileImports($primitiveTypeInterface, $typeValidationTrait); $xmlName = NameUtils::getTypeXMLElementName($type); @@ -47,6 +47,8 @@ class getClassName(); ?> implements ; + private const _FHIR_VALIDATION_RULES = []; + /** @var string */ protected string $value; diff --git a/template/versions/types/header.php b/template/versions/types/header.php index 67454670..0de2fc5d 100644 --- a/template/versions/types/header.php +++ b/template/versions/types/header.php @@ -21,6 +21,10 @@ /** @var \DCarbone\PHPFHIR\Version $version */ /** @var \DCarbone\PHPFHIR\Version\Definition\Type $type */ +$versionCoreFiles = $version->getCoreFiles(); + +$versionClass = $versionCoreFiles->getCoreFileByEntityName(PHPFHIR_VERSION_CLASSNAME_VERSION); + $parentType = $type->getParentType(); $classDocumentation = $type->getDocBlockDocumentationFragment(1, true); $interfaces = $type->getDirectlyImplementedInterfaces(); diff --git a/template/versions/types/properties/methods/default.php b/template/versions/types/properties/methods/default.php index ce02f32b..3d1b0459 100644 --- a/template/versions/types/properties/methods/default.php +++ b/template/versions/types/properties/methods/default.php @@ -163,6 +163,16 @@ public function set([] = new (value: $v); } } +isResourceContainer($version)) : ?> + foreach($ as $v) { + if ($v instanceof ) { + $v = $v->getContainedType(); + } + if (null !== $v) { + $this->[] = $v; + } + } $this-> = $; diff --git a/template/versions/types/serialization/json/unserialize/body.php b/template/versions/types/serialization/json/unserialize/body.php index cba5b998..9569d8b9 100644 --- a/template/versions/types/serialization/json/unserialize/body.php +++ b/template/versions/types/serialization/json/unserialize/body.php @@ -98,12 +98,12 @@ $vals = $json->; } foreach($vals as $v) { - $typeClassName = ::getContainedTypeClassNameFromJSON($v); + $typeClassName = ::mustGetContainedTypeClassnameFromJSON($v); unset($v->); $type->($typeClassName::jsonUnserialize($v, $config)); } - $typeClassName = ::getContainedTypeClassNameFromJSON($json->); + $typeClassName = ::mustGetContainedTypeClassnameFromJSON($json->); $v = $json->; unset($v->); $type->($typeClassName::jsonUnserialize($v, $config)); diff --git a/template/versions/types/serialization/xml/unserialize/body.php b/template/versions/types/serialization/xml/unserialize/body.php index 57e9f09a..164e5208 100644 --- a/template/versions/types/serialization/xml/unserialize/body.php +++ b/template/versions/types/serialization/xml/unserialize/body.php @@ -49,7 +49,7 @@ isResourceContainer($version)) : ?> foreach ($ce->children() as $cen) { /** @var getFullyQualifiedName(true); ?> $cn */ - $cn = ::getContainedTypeClassNameFromXML($cen); + $cn = ::mustGetContainedTypeClassnameFromXML($cen); $type->($cn::xmlUnserialize($cen, $config)); }