Skip to content

Commit

Permalink
Extract FieldMapping in its own DTO
Browse files Browse the repository at this point in the history
  • Loading branch information
greg0ire committed Jan 17, 2023
1 parent 3cd65b1 commit bb61b9a
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 98 deletions.
89 changes: 12 additions & 77 deletions lib/Doctrine/ORM/Mapping/ClassMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
use function in_array;
use function interface_exists;
use function is_array;
use function is_string;
use function is_subclass_of;
use function ltrim;
use function method_exists;
Expand All @@ -66,29 +67,6 @@
*
* @template-covariant T of object
* @template-implements PersistenceClassMetadata<T>
* @psalm-type FieldMapping = array{
* type: string,
* fieldName: string,
* columnName: string,
* length?: int,
* id?: bool,
* nullable?: bool,
* notInsertable?: bool,
* notUpdatable?: bool,
* generated?: int,
* enumType?: class-string<BackedEnum>,
* columnDefinition?: string,
* precision?: int,
* scale?: int,
* unique?: string,
* inherited?: class-string,
* originalClass?: class-string,
* originalField?: string,
* quoted?: bool,
* declared?: class-string,
* declaredField?: string,
* options?: array<string, mixed>
* }
* @psalm-type JoinColumnData = array{
* name: string,
* referencedColumnName: string,
Expand Down Expand Up @@ -399,51 +377,9 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable

/**
* READ-ONLY: The field mappings of the class.
* Keys are field names and values are mapping definitions.
*
* The mapping definition array has the following values:
*
* - <b>fieldName</b> (string)
* The name of the field in the Entity.
*
* - <b>type</b> (string)
* The type name of the mapped field. Can be one of Doctrine's mapping types
* or a custom mapping type.
*
* - <b>columnName</b> (string, optional)
* The column name. Optional. Defaults to the field name.
*
* - <b>length</b> (integer, optional)
* The database length of the column. Optional. Default value taken from
* the type.
*
* - <b>id</b> (boolean, optional)
* Marks the field as the primary key of the entity. Multiple fields of an
* entity can have the id attribute, forming a composite key.
*
* - <b>nullable</b> (boolean, optional)
* Whether the column is nullable. Defaults to FALSE.
*
* - <b>'notInsertable'</b> (boolean, optional)
* Whether the column is not insertable. Optional. Is only set if value is TRUE.
* Keys are field names and values are FieldMapping instances
*
* - <b>'notUpdatable'</b> (boolean, optional)
* Whether the column is updatable. Optional. Is only set if value is TRUE.
*
* - <b>columnDefinition</b> (string, optional, schema-only)
* The SQL fragment that is used when generating the DDL for the column.
*
* - <b>precision</b> (integer, optional, schema-only)
* The precision of a decimal column. Only valid if the column type is decimal.
*
* - <b>scale</b> (integer, optional, schema-only)
* The scale of a decimal column. Only valid if the column type is decimal.
*
* - <b>'unique'</b> (string, optional, schema-only)
* Whether a unique constraint should be generated for the column.
*
* @var mixed[]
* @psalm-var array<string, FieldMapping>
* @var array<string, FieldMapping>
*/
public array $fieldMappings = [];

Expand Down Expand Up @@ -1206,12 +1142,9 @@ public function getColumnName(string $fieldName): string
* Gets the mapping of a (regular) field that holds some data but not a
* reference to another object.
*
* @return mixed[] The field mapping.
* @psalm-return FieldMapping
*
* @throws MappingException
*/
public function getFieldMapping(string $fieldName): array
public function getFieldMapping(string $fieldName): FieldMapping
{
if (! isset($this->fieldMappings[$fieldName])) {
throw MappingException::mappingNotFound($this->name, $fieldName);
Expand Down Expand Up @@ -1322,11 +1255,11 @@ private function validateAndCompleteTypedAssociationMapping(array $mapping): arr
* enumType?: class-string,
* } $mapping The field mapping to validate & complete.
*
* @return mixed[] The updated mapping.
* @return FieldMapping The updated mapping.
*
* @throws MappingException
*/
protected function validateAndCompleteFieldMapping(array $mapping): array
protected function validateAndCompleteFieldMapping(array $mapping): FieldMapping
{
// Check mandatory fields
if (! isset($mapping['fieldName']) || ! $mapping['fieldName']) {
Expand All @@ -1347,6 +1280,8 @@ protected function validateAndCompleteFieldMapping(array $mapping): array
$mapping['columnName'] = $this->namingStrategy->propertyToColumnName($mapping['fieldName'], $this->name);
}

$mapping = FieldMapping::fromMappingArray($mapping);

if ($mapping['columnName'][0] === '`') {
$mapping['columnName'] = trim($mapping['columnName'], '`');
$mapping['quoted'] = true;
Expand Down Expand Up @@ -1476,6 +1411,7 @@ protected function _validateAndCompleteAssociationMapping(array $mapping)
);
}

assert(is_string($mapping['fieldName']));
$this->identifier[] = $mapping['fieldName'];
$this->containsForeignIdentifier = true;
}
Expand Down Expand Up @@ -2354,10 +2290,8 @@ public function addInheritedAssociationMapping(array $mapping/*, $owningClassNam
* INTERNAL:
* Adds a field mapping without completing/validating it.
* This is mainly used to add inherited field mappings to derived classes.
*
* @psalm-param array<string, mixed> $fieldMapping
*/
public function addInheritedFieldMapping(array $fieldMapping): void
public function addInheritedFieldMapping(FieldMapping $fieldMapping): void
{
$this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
$this->columnNames[$fieldMapping['fieldName']] = $fieldMapping['columnName'];
Expand Down Expand Up @@ -2934,7 +2868,8 @@ public function mapEmbedded(array $mapping): void
*/
public function inlineEmbeddable(string $property, ClassMetadata $embeddable): void
{
foreach ($embeddable->fieldMappings as $fieldMapping) {
foreach ($embeddable->fieldMappings as $originalFieldMapping) {
$fieldMapping = (array) $originalFieldMapping;
$fieldMapping['originalClass'] ??= $embeddable->name;
$fieldMapping['declaredField'] = isset($fieldMapping['declaredField'])
? $property . '.' . $fieldMapping['declaredField']
Expand Down
11 changes: 6 additions & 5 deletions lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -328,15 +328,16 @@ private function getShortName(string $className): string
private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass): void
{
foreach ($parentClass->fieldMappings as $mapping) {
if (! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
$mapping['inherited'] = $parentClass->name;
$subClassMapping = clone $mapping;
if (! isset($subClassMapping['inherited']) && ! $parentClass->isMappedSuperclass) {
$subClassMapping['inherited'] = $parentClass->name;
}

if (! isset($mapping['declared'])) {
$mapping['declared'] = $parentClass->name;
if (! isset($subClassMapping['declared'])) {
$subClassMapping['declared'] = $parentClass->name;
}

$subClass->addInheritedFieldMapping($mapping);
$subClass->addInheritedFieldMapping($subClassMapping);
}

foreach ($parentClass->reflFields as $name => $field) {
Expand Down
117 changes: 117 additions & 0 deletions lib/Doctrine/ORM/Mapping/FieldMapping.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

declare(strict_types=1);

namespace Doctrine\ORM\Mapping;

use ArrayAccess;

use function in_array;
use function property_exists;

/** @template-implements ArrayAccess<string, mixed> */
final class FieldMapping implements ArrayAccess
{
/** @var int|null The database length of the column. Optional. Default value taken from the type. */
public int|null $length = null;
/**
* @var bool|null Marks the field as the primary key of the entity. Multiple
* fields of an entity can have the id attribute, forming a composite key.
*/
public bool|null $id = null;
public bool|null $nullable = null;
public bool|null $notInsertable = null;
public bool|null $notUpdatable = null;
public string|null $columnDefinition = null;
/** ClassMetadata::GENERATED_* */
public int|null $generated = null;
/** @var class-string|null */
public string|null $enumType = null;
/**
* @var int|null The precision of a decimal column.
* Only valid if the column type is decimal
*/
public int|null $precision = null;
/**
* @var int|null The scale of a decimal column.
* Only valid if the column type is decimal
*/
public int|null $scale = null;
/** @var bool|null Whether a unique constraint should be generated for the column. */
public bool|null $unique = null;
/** @var class-string|null */
public string|null $inherited = null;

public string|null $originalClass = null;
public string|null $originalField = null;
public bool|null $quoted = null;
/** @var class-string|null */
public string|null $declared = null;
public string|null $declaredField = null;
public array|null $options = null;
public bool|null $version = null;
public string|int|null $default = null;

/**
* @param string $type The type name of the mapped field. Can be one of
* Doctrine's mapping types or a custom mapping type.
* @param string $fieldName The name of the field in the Entity.
* @param string $columnName The column name. Optional. Defaults to the field name.
*/
public function __construct(
public string $type,
public string $fieldName,
public string $columnName,
) {
}

/** @param array{type: string, fieldName: string, columnName: string} $mappingArray */
public static function fromMappingArray(array $mappingArray): self
{
$mapping = new self(
$mappingArray['type'],
$mappingArray['fieldName'],
$mappingArray['columnName'],
);
foreach ($mappingArray as $key => $value) {
if (in_array($key, ['type', 'fieldName', 'columnName'])) {
continue;
}

if (property_exists($mapping, $key)) {
$mapping->$key = $value;
}
}

return $mapping;
}

/**
* {@inheritDoc}
*/
public function offsetExists($offset): bool
{
return isset($this->$offset);
}

public function offsetGet($offset): mixed
{
return $this->$offset;
}

/**
* {@inheritDoc}
*/
public function offsetSet($offset, $value): void
{
$this->$offset = $value;
}

/**
* {@inheritDoc}
*/
public function offsetUnset($offset): void
{
$this->$offset = null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1845,7 +1845,7 @@ private function getTypes(string $field, mixed $value, ClassMetadata $class): ar

switch (true) {
case isset($class->fieldMappings[$field]):
$types = array_merge($types, [$class->fieldMappings[$field]['type']]);
$types = array_merge($types, [$class->fieldMappings[$field]->type]);
break;

case isset($class->associationMappings[$field]):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\FieldMapping;
use Doctrine\Persistence\Mapping\MappingException;
use InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
Expand Down Expand Up @@ -242,7 +243,7 @@ private function formatField(string $label, mixed $value): array
/**
* Format the association mappings
*
* @psalm-param array<string, array<string, mixed>> $propertyMappings
* @psalm-param array<string, FieldMapping|array<string, mixed>> $propertyMappings
*
* @return string[][]
* @psalm-return list<array{0: string, 1: string}>
Expand All @@ -254,7 +255,7 @@ private function formatMappings(array $propertyMappings): array
foreach ($propertyMappings as $propertyName => $mapping) {
$output[] = $this->formatField(sprintf(' %s', $propertyName), '');

foreach ($mapping as $field => $value) {
foreach ((array) $mapping as $field => $value) {
$output[] = $this->formatField(sprintf(' %s', $field), $this->formatValue($value));
}
}
Expand Down
9 changes: 5 additions & 4 deletions lib/Doctrine/ORM/Tools/SchemaTool.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\FieldMapping;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\ORM\Mapping\QuoteStrategy;
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
Expand Down Expand Up @@ -449,11 +450,11 @@ private function gatherColumns(ClassMetadata $class, Table $table): void
* Creates a column definition as required by the DBAL from an ORM field mapping definition.
*
* @param ClassMetadata $class The class that owns the field mapping.
* @psalm-param array<string, mixed> $mapping The field mapping.
* @psalm-param FieldMapping $mapping The field mapping.
*/
private function gatherColumn(
ClassMetadata $class,
array $mapping,
FieldMapping $mapping,
Table $table,
): void {
$columnName = $this->quoteStrategy->getColumnName($mapping['fieldName'], $class, $this->platform);
Expand Down Expand Up @@ -768,11 +769,11 @@ private function gatherRelationJoinColumns(
}

/**
* @param mixed[] $mapping
* @param FieldMapping|mixed[] $mapping
*
* @return mixed[]
*/
private function gatherColumnOptions(array $mapping): array
private function gatherColumnOptions(FieldMapping|array $mapping): array
{
$mappingOptions = $mapping['options'] ?? [];

Expand Down
2 changes: 1 addition & 1 deletion lib/Doctrine/ORM/Utility/PersisterHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class PersisterHelper
public static function getTypeOfField(string $fieldName, ClassMetadata $class, EntityManagerInterface $em): array
{
if (isset($class->fieldMappings[$fieldName])) {
return [$class->fieldMappings[$fieldName]['type']];
return [$class->fieldMappings[$fieldName]->type];
}

if (! isset($class->associationMappings[$fieldName])) {
Expand Down
Loading

0 comments on commit bb61b9a

Please sign in to comment.