@@ -388,19 +388,33 @@ public function generateModelClass(array $structureDefinition, string $version,
388388 $ class ->addMethod ('__construct ' );
389389 $ parentParameters = [];
390390
391+ // Build a set of param names the PHP parent constructor actually accepts.
392+ // This prevents passing CanonicalResource-inherited params to DomainResourceResource
393+ // when a FHIR type (e.g. MetadataResource) lists both as ancestors via base.path.
394+ $ validParentParamNames = [];
395+ if (isset ($ parentClass ) && $ parentClass ->class instanceof ClassType) {
396+ try {
397+ $ validParentParamNames = array_keys ($ parentClass ->class ->getMethod ('__construct ' )->getParameters ());
398+ } catch (\Throwable ) {
399+ // Parent has no __construct — pass all params as before
400+ }
401+ }
402+
391403 if (isset ($ structureDefinition ['snapshot ' ]) === true ) {
392404 $ elements = $ this ->nestElements ($ structureDefinition ['snapshot ' ]['element ' ]);
393405
394406 foreach ($ elements ['_properties ' ] as $ property ) {
395- $ element = $ property ['_element ' ];
407+ $ element = $ property ['_element ' ];
408+ $ derivedParam = $ this ->convertToMethodName ($ element ['base ' ]['path ' ]);
396409 if (
397410 $ element ['path ' ] !== $ element ['base ' ]['path ' ]
398411 && ! in_array ($ element ['path ' ], $ parentParameters , true )
399412 && $ element ['max ' ] !== '0 '
413+ && ($ validParentParamNames === [] || in_array ($ derivedParam , $ validParentParamNames , true ))
400414 ) {
401- $ parentParameters [] = $ this -> convertToMethodName ( $ element [ ' base ' ][ ' path ' ]) ;
415+ $ parentParameters [] = $ derivedParam ;
402416 }
403- $ this ->createForElement ($ class , $ property ['_element ' ], $ property [ ' _properties ' ], $ version , $ builderContext , $ sdUrl );
417+ $ this ->createForElement ($ class , $ property ['_properties ' ], $ version , $ builderContext , $ sdUrl, $ validParentParamNames );
404418 }
405419 }
406420
@@ -409,14 +423,15 @@ public function generateModelClass(array $structureDefinition, string $version,
409423
410424 /**
411425 * @param ClassType $classType
412- * @param array<string, mixed> $classElement
413426 * @param array<string,array<string, mixed>> $propertyElements
414427 * @param string $version
415428 * @param BuilderContextInterface $builderContext
429+ * @param list<string> $validParentParamNames when non-empty, only elements whose derived parameter
430+ * name appears in this list are added to the parent::__construct() call
416431 *
417432 * @return ClassType
418433 */
419- public function createForElement (ClassType $ classType , array $ classElement , array $ propertyElements , string $ version , BuilderContextInterface $ builderContext , ?string $ sdUrl = null ): ClassType
434+ public function createForElement (ClassType $ classType , array $ propertyElements , string $ version , BuilderContextInterface $ builderContext , ?string $ sdUrl = null , array $ validParentParamNames = [] ): ClassType
420435 {
421436 $ constructor = $ classType ->getMethod ('__construct ' );
422437 $ parentParameters = [];
@@ -437,14 +452,16 @@ public function createForElement(ClassType $classType, array $classElement, arra
437452 // Track ValueSet dependencies for primitive elements with bindings
438453 $ this ->trackValueSetDependencies ($ element , $ builderContext );
439454
455+ $ derivedParam = $ this ->convertToMethodName ($ element ['base ' ]['path ' ]);
440456 if (
441457 $ element ['path ' ] !== $ element ['base ' ]['path ' ]
442458 && ! in_array ($ element ['path ' ], $ parentParameters , true )
443459 && $ element ['max ' ] !== '0 '
460+ && ($ validParentParamNames === [] || in_array ($ derivedParam , $ validParentParamNames , true ))
444461 ) {
445- $ parentParameters [] = $ this -> convertToMethodName ( $ element [ ' base ' ][ ' path ' ]) ;
462+ $ parentParameters [] = $ derivedParam ;
446463 }
447- $ this ->addElementAsProperty ($ propertyElement ['_element ' ], $ constructor , $ version , $ builderContext, $ classNamespace );
464+ $ this ->addElementAsProperty ($ propertyElement ['_element ' ], $ constructor , $ version , $ builderContext );
448465 } else {
449466 $ element = $ propertyElement ['_element ' ];
450467
@@ -515,14 +532,16 @@ public function createForElement(ClassType $classType, array $classElement, arra
515532 if (isset ($ element ['definition ' ])) {
516533 $ childClass ->addComment ('@description ' . $ element ['definition ' ]);
517534 }
535+ $ derivedParam = $ this ->convertToMethodName ($ element ['base ' ]['path ' ]);
518536 if (
519537 $ element ['path ' ] !== $ element ['base ' ]['path ' ]
520538 && ! in_array ($ element ['path ' ], $ parentParameters , true )
521539 && $ element ['max ' ] !== '0 '
540+ && ($ validParentParamNames === [] || in_array ($ derivedParam , $ validParentParamNames , true ))
522541 ) {
523- $ parentParameters [] = $ this -> convertToMethodName ( $ element [ ' base ' ][ ' path ' ]) ;
542+ $ parentParameters [] = $ derivedParam ;
524543 }
525- $ this ->addElementAsProperty ($ element , $ constructor , $ version , $ builderContext, $ classNamespace );
544+ $ this ->addElementAsProperty ($ element , $ constructor , $ version , $ builderContext );
526545
527546 // Emit FHIRPathInvariant attributes on child classes from their element constraints.
528547 foreach ($ element ['constraint ' ] ?? [] as $ constraint ) {
@@ -544,7 +563,7 @@ public function createForElement(ClassType $classType, array $classElement, arra
544563
545564 if (isset ($ propertyElement ['_properties ' ])) {
546565 // Recursively process nested elements for ValueSet dependencies
547- $ this ->createForElement ($ childClass , $ element , $ propertyElement ['_properties ' ], $ version , $ builderContext , $ sdUrl );
566+ $ this ->createForElement ($ childClass , $ propertyElement ['_properties ' ], $ version , $ builderContext , $ sdUrl );
548567 }
549568 }
550569 }
@@ -693,10 +712,9 @@ private function trackProfileBindings(string $profileUrl, BuilderContextInterfac
693712 * @param Method $method
694713 * @param string $version
695714 * @param BuilderContextInterface $builderContext
696- * @param PhpNamespace $namespace
697715 * @param EnumType|null $enum
698716 */
699- private function addElementAsProperty (array $ element , Method $ method , string $ version , BuilderContextInterface $ builderContext , PhpNamespace $ namespace , ?EnumType $ enum = null ): void
717+ private function addElementAsProperty (array $ element , Method $ method , string $ version , BuilderContextInterface $ builderContext , ?EnumType $ enum = null ): void
700718 {
701719 $ types = [];
702720 if (! isset ($ element ['type ' ]) && isset ($ element ['contentReference ' ])) {
@@ -711,7 +729,7 @@ private function addElementAsProperty(array $element, Method $method, string $ve
711729 $ relatedNamespace = $ relatedClass ->namespace ;
712730 $ types [] = '\\' . $ relatedNamespace . '\\' . $ relatedClass ->asClassType ()->getName ();
713731 } elseif (isset ($ element ['type ' ])) {
714- $ types = $ this ->resolveClassFromType ($ element , $ builderContext , $ version , $ types , $ enum, $ namespace );
732+ $ types = $ this ->resolveClassFromType ($ element , $ builderContext , $ version , $ types , $ enum );
715733 }
716734
717735 $ parameterName = $ this ->convertToMethodName ($ element ['path ' ]);
@@ -1225,13 +1243,9 @@ private function getNamespaceForFhirType(string $code, string $version, BuilderC
12251243 'ElementDefinition ' ,
12261244 'ProductShelfLife ' ,
12271245 'MarketingStatus ' ,
1228- ];
1229-
1230- // Types that remain in Resource namespace across all versions
1231- $ typesAlwaysInResource = [
1232- 'SubstanceAmount ' , // Stays in Resource even in R5
1233- 'ProdCharacteristic ' , // Stays in Resource even in R5
1234- 'Population ' , // Stays in Resource even in R5
1246+ 'SubstanceAmount ' , // complex-type in all FHIR versions; generated into DataType
1247+ 'ProdCharacteristic ' , // complex-type in all FHIR versions; generated into DataType
1248+ 'Population ' , // complex-type in all FHIR versions; generated into DataType
12351249 ];
12361250
12371251 if (in_array ($ code , $ typesInDataTypeNamespace , true )) {
@@ -1243,11 +1257,6 @@ private function getNamespaceForFhirType(string $code, string $version, BuilderC
12431257 }
12441258 }
12451259
1246- if (in_array ($ code , $ typesAlwaysInResource , true )) {
1247- // These types always stay in Resource namespace
1248- return $ builderContext ->getElementNamespace ($ version )->getName ();
1249- }
1250-
12511260 // List of known FHIR primitive types
12521261 $ primitiveTypes = [
12531262 'boolean ' ,
@@ -1398,18 +1407,16 @@ private function shouldEmitBindingAttribute(string $bindingStrength): bool
13981407 * Handles ValueSet resolution with versioned URLs, fallback to string type
13991408 * when ValueSet cannot be resolved, and proper enum/code type generation.
14001409 *
1401- * @param string $valueSetUrl The ValueSet URL (may include version)
1402- * @param BuilderContextInterface $builderContext The builder context
1403- * @param string $version The FHIR version
1404- * @param string $targetElementNamespace The target element namespace
1410+ * @param string $valueSetUrl The ValueSet URL (may include version)
1411+ * @param BuilderContextInterface $builderContext The builder context
1412+ * @param string $version The FHIR version
14051413 *
14061414 * @return string The resolved code type (class name or 'string')
14071415 */
14081416 private function resolveValueSetCodeType (
14091417 string $ valueSetUrl ,
14101418 BuilderContextInterface $ builderContext ,
14111419 string $ version ,
1412- string $ targetElementNamespace
14131420 ): string {
14141421 // Code type wrappers are in DataType namespace since they extend FHIRCode
14151422 $ dataTypeNamespace = $ builderContext ->getDatatypeNamespace ($ version )->getName ();
@@ -1596,17 +1603,15 @@ private function nestElements(array $elements): array
15961603 * @param string $version
15971604 * @param array<string> $types
15981605 * @param EnumType|null $enum
1599- * @param PhpNamespace $namespace
16001606 *
16011607 * @return array<string>
16021608 */
1603- public function resolveClassFromType (array $ element , BuilderContextInterface $ builderContext , string $ version , array $ types , ?EnumType $ enum, PhpNamespace $ namespace ): array
1609+ public function resolveClassFromType (array $ element , BuilderContextInterface $ builderContext , string $ version , array $ types , ?EnumType $ enum ): array
16041610 {
16051611 foreach ($ element ['type ' ] as $ type ) {
16061612 $ code = $ type ['code ' ];
16071613
1608- $ targetElementNamespace = $ builderContext ->getElementNamespace ($ version )->getName ();
1609- $ targetEnumNamespace = $ builderContext ->getEnumNamespace ($ version )->getName ();
1614+ $ targetEnumNamespace = $ builderContext ->getEnumNamespace ($ version )->getName ();
16101615 if ($ code === 'http://hl7.org/fhirpath/System.String ' ) {
16111616 if (isset ($ element ['base ' ]['path ' ]) && $ element ['base ' ]['path ' ] === 'integer.value ' ) {
16121617 $ types [] = 'int ' ;
@@ -1696,7 +1701,7 @@ public function resolveClassFromType(array $element, BuilderContextInterface $bu
16961701 // Only generate enums for required binding strength
16971702 if ($ this ->shouldGenerateEnumForBinding ($ bindingStrength )) {
16981703 $ valueSetUrl = $ element ['binding ' ]['valueSet ' ];
1699- $ codeType = $ this ->resolveValueSetCodeType ($ valueSetUrl , $ builderContext , $ version, $ targetElementNamespace );
1704+ $ codeType = $ this ->resolveValueSetCodeType ($ valueSetUrl , $ builderContext , $ version );
17001705 } else {
17011706 // For extensible, preferred, and example bindings, use string type
17021707 $ codeType = 'string ' ;
0 commit comments