1515use InvalidArgumentException ;
1616use ReflectionClass ;
1717use ReflectionMethod ;
18+ use ReflectionProperty ;
1819
1920use function assert ;
2021use function class_exists ;
@@ -38,8 +39,11 @@ class AttributeDriver implements MappingDriver
3839 * @param array<string> $paths
3940 * @param true $reportFieldsWhereDeclared no-op, to be removed in 4.0
4041 */
41- public function __construct (array $ paths , bool $ reportFieldsWhereDeclared = true )
42- {
42+ public function __construct (
43+ array $ paths ,
44+ bool $ reportFieldsWhereDeclared = true ,
45+ private readonly bool $ inheritNullabilityFromPropertyType = false ,
46+ ) {
4347 if (! $ reportFieldsWhereDeclared ) {
4448 throw new InvalidArgumentException (sprintf (
4549 'The $reportFieldsWhereDeclared argument is no longer supported, make sure to omit it when calling %s. ' ,
@@ -297,7 +301,7 @@ public function loadMetadataForClass(string $className, PersistenceClassMetadata
297301 $ joinColumnAttributes = $ this ->reader ->getPropertyAttributeCollection ($ property , Mapping \JoinColumn::class);
298302
299303 foreach ($ joinColumnAttributes as $ joinColumnAttribute ) {
300- $ joinColumns [] = $ this ->joinColumnToArray ($ joinColumnAttribute );
304+ $ joinColumns [] = $ this ->joinColumnToArray ($ joinColumnAttribute, $ property );
301305 }
302306
303307 // Field can only be attributed with one of:
@@ -309,8 +313,17 @@ public function loadMetadataForClass(string $className, PersistenceClassMetadata
309313 $ manyToManyAttribute = $ this ->reader ->getPropertyAttribute ($ property , Mapping \ManyToMany::class);
310314 $ embeddedAttribute = $ this ->reader ->getPropertyAttribute ($ property , Mapping \Embedded::class);
311315
316+ // If the property has a type declaration, and no explicit JoinColumn attributes are set, we can infer nullability from the type declaration
317+ if ($ this ->inheritNullabilityFromPropertyType && empty ($ joinColumns ) && $ property ->hasType () && ($ oneToOneAttribute !== null || $ manyToOneAttribute !== null )) {
318+ $ joinColumns = [
319+ [
320+ 'nullable ' => $ property ->getType ()->allowsNull (),
321+ ],
322+ ];
323+ }
324+
312325 if ($ columnAttribute !== null ) {
313- $ mapping = $ this ->columnToArray ($ property ->name , $ columnAttribute );
326+ $ mapping = $ this ->columnToArray ($ property ->name , $ columnAttribute, $ property );
314327
315328 if ($ this ->reader ->getPropertyAttribute ($ property , Mapping \Id::class)) {
316329 $ mapping ['id ' ] = true ;
@@ -680,12 +693,12 @@ private function getMethodCallbacks(ReflectionMethod $method): array
680693 * options?: array<string, mixed>
681694 * }
682695 */
683- private function joinColumnToArray (Mapping \JoinColumn |Mapping \InverseJoinColumn $ joinColumn ): array
696+ private function joinColumnToArray (Mapping \JoinColumn |Mapping \InverseJoinColumn $ joinColumn, ReflectionProperty | null $ property = null ): array
684697 {
685698 $ mapping = [
686699 'name ' => $ joinColumn ->name ,
687700 'unique ' => $ joinColumn ->unique ,
688- 'nullable ' => $ joinColumn ->nullable ,
701+ 'nullable ' => $ this -> detectNullability ( $ property , $ joinColumn ->nullable , $ joinColumn -> nullableSet ) ,
689702 'onDelete ' => $ joinColumn ->onDelete ,
690703 'columnDefinition ' => $ joinColumn ->columnDefinition ,
691704 'referencedColumnName ' => $ joinColumn ->referencedColumnName ,
@@ -716,15 +729,15 @@ private function joinColumnToArray(Mapping\JoinColumn|Mapping\InverseJoinColumn
716729 * columnDefinition?: string
717730 * }
718731 */
719- private function columnToArray (string $ fieldName , Mapping \Column $ column ): array
732+ private function columnToArray (string $ fieldName , Mapping \Column $ column, ReflectionProperty | null $ property = null ): array
720733 {
721734 $ mapping = [
722735 'fieldName ' => $ fieldName ,
723736 'type ' => $ column ->type ,
724737 'scale ' => $ column ->scale ,
725738 'length ' => $ column ->length ,
726739 'unique ' => $ column ->unique ,
727- 'nullable ' => $ column ->nullable ,
740+ 'nullable ' => $ this -> detectNullability ( $ property , $ column ->nullable , $ column -> nullableSet ) ,
728741 'precision ' => $ column ->precision ,
729742 ];
730743
@@ -758,4 +771,18 @@ private function columnToArray(string $fieldName, Mapping\Column $column): array
758771
759772 return $ mapping ;
760773 }
774+
775+ private function detectNullability (ReflectionProperty |null $ property , bool $ default , bool $ wasSet ): bool
776+ {
777+ if (
778+ $ this ->inheritNullabilityFromPropertyType
779+ && ! $ wasSet
780+ && $ property !== null
781+ && $ property ->hasType ()
782+ ) {
783+ return $ property ->getType ()->allowsNull ();
784+ }
785+
786+ return $ default ;
787+ }
761788}
0 commit comments