mirror of
https://github.com/doctrine/orm.git
synced 2026-03-24 06:52:09 +01:00
Generated/Virtual Columns: Insertable / Updateable (#9118)
* Generated/Virtual Columns: Insertable / Updateable Defines whether a column is included in an SQL INSERT and/or UPDATE statement. Throws an exception for UPDATE statements attempting to update this field/column. Closes #5728 * Apply suggestions from code review Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr> * Add example for virtual column usage in attributes to docs. Co-authored-by: Benjamin Eberlei <kontakt@beberlei.de> Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
This commit is contained in:
committed by
GitHub
parent
ec391be4f2
commit
e369cb6e73
@@ -123,6 +123,18 @@ Optional attributes:
|
||||
|
||||
- **nullable**: Determines if NULL values allowed for this column. If not specified, default value is false.
|
||||
|
||||
- **insertable**: Boolean value to determine if the column should be
|
||||
included when inserting a new row into the underlying entities table.
|
||||
If not specified, default value is true.
|
||||
|
||||
- **updatable**: Boolean value to determine if the column should be
|
||||
included when updating the row of the underlying entities table.
|
||||
If not specified, default value is true.
|
||||
|
||||
- **generated**: An enum with the possible values ALWAYS, INSERT, NEVER. Is
|
||||
used after an INSERT or UPDATE statement to determine if the database
|
||||
generated this value and it needs to be fetched using a SELECT statement.
|
||||
|
||||
- **options**: Array of additional options:
|
||||
|
||||
- ``default``: The default value to set for the column if no value
|
||||
@@ -193,6 +205,13 @@ Examples:
|
||||
*/
|
||||
protected $loginCount;
|
||||
|
||||
/**
|
||||
* Generated column
|
||||
* @Column(type="string", name="user_fullname", insertable=false, updatable=false)
|
||||
* MySQL example: full_name char(41) GENERATED ALWAYS AS (concat(firstname,' ',lastname)),
|
||||
*/
|
||||
protected $fullname;
|
||||
|
||||
.. _annref_column_result:
|
||||
|
||||
@ColumnResult
|
||||
|
||||
@@ -178,6 +178,18 @@ Optional parameters:
|
||||
- **nullable**: Determines if NULL values allowed for this column.
|
||||
If not specified, default value is ``false``.
|
||||
|
||||
- **insertable**: Boolean value to determine if the column should be
|
||||
included when inserting a new row into the underlying entities table.
|
||||
If not specified, default value is true.
|
||||
|
||||
- **updatable**: Boolean value to determine if the column should be
|
||||
included when updating the row of the underlying entities table.
|
||||
If not specified, default value is true.
|
||||
|
||||
- **generated**: An enum with the possible values ALWAYS, INSERT, NEVER. Is
|
||||
used after an INSERT or UPDATE statement to determine if the database
|
||||
generated this value and it needs to be fetched using a SELECT statement.
|
||||
|
||||
- **options**: Array of additional options:
|
||||
|
||||
- ``default``: The default value to set for the column if no value
|
||||
@@ -248,6 +260,15 @@ Examples:
|
||||
)]
|
||||
protected $loginCount;
|
||||
|
||||
// MySQL example: full_name char(41) GENERATED ALWAYS AS (concat(firstname,' ',lastname)),
|
||||
#[Column(
|
||||
type: "string",
|
||||
name: "user_fullname",
|
||||
insertable: false,
|
||||
updatable: false
|
||||
)]
|
||||
protected $fullname;
|
||||
|
||||
.. _attrref_cache:
|
||||
|
||||
#[Cache]
|
||||
|
||||
@@ -199,6 +199,10 @@ list:
|
||||
unique key.
|
||||
- ``nullable``: (optional, default FALSE) Whether the database
|
||||
column is nullable.
|
||||
- ``insertable``: (optional, default TRUE) Whether the database
|
||||
column should be inserted.
|
||||
- ``updatable``: (optional, default TRUE) Whether the database
|
||||
column should be updated.
|
||||
- ``enumType``: (optional, requires PHP 8.1 and ORM 2.11) The PHP enum type
|
||||
name to convert the database value into.
|
||||
- ``precision``: (optional, default 0) The precision for a decimal
|
||||
|
||||
@@ -256,6 +256,11 @@ Optional attributes:
|
||||
table? Defaults to false.
|
||||
- nullable - Should this field allow NULL as a value? Defaults to
|
||||
false.
|
||||
- insertable - Should this field be inserted? Defaults to true.
|
||||
- updatable - Should this field be updated? Defaults to true.
|
||||
- generated - Enum of the values ALWAYS, INSERT, NEVER that determines if
|
||||
generated value must be fetched from database after INSERT or UPDATE.
|
||||
Defaults to "NEVER".
|
||||
- version - Should this field be used for optimistic locking? Only
|
||||
works on fields with type integer or datetime.
|
||||
- scale - Scale of a decimal type.
|
||||
|
||||
@@ -288,6 +288,14 @@
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="generated-type">
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:enumeration value="NEVER"/>
|
||||
<xs:enumeration value="INSERT"/>
|
||||
<xs:enumeration value="ALWAYS"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:complexType name="field">
|
||||
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:element name="options" type="orm:options" minOccurs="0" />
|
||||
@@ -299,6 +307,9 @@
|
||||
<xs:attribute name="length" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="unique" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="nullable" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="insertable" type="xs:boolean" default="true" />
|
||||
<xs:attribute name="updatable" type="xs:boolean" default="true" />
|
||||
<xs:attribute name="generated" type="orm:generated-type" default="NEVER" />
|
||||
<xs:attribute name="enum-type" type="xs:string" />
|
||||
<xs:attribute name="version" type="xs:boolean" />
|
||||
<xs:attribute name="column-definition" type="xs:string" />
|
||||
@@ -623,6 +634,8 @@
|
||||
<xs:attribute name="length" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="unique" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="nullable" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="insertable" type="xs:boolean" default="true" />
|
||||
<xs:attribute name="updateable" type="xs:boolean" default="true" />
|
||||
<xs:attribute name="version" type="xs:boolean" />
|
||||
<xs:attribute name="column-definition" type="xs:string" />
|
||||
<xs:attribute name="precision" type="xs:integer" use="optional" />
|
||||
|
||||
@@ -55,8 +55,16 @@ class DefaultEntityHydrator implements EntityHydrator
|
||||
$data = $this->uow->getOriginalEntityData($entity);
|
||||
$data = array_merge($data, $metadata->getIdentifierValues($entity)); // why update has no identifier values ?
|
||||
|
||||
if ($metadata->isVersioned) {
|
||||
$data[$metadata->versionField] = $metadata->getFieldValue($entity, $metadata->versionField);
|
||||
if ($metadata->requiresFetchAfterChange) {
|
||||
if ($metadata->isVersioned) {
|
||||
$data[$metadata->versionField] = $metadata->getFieldValue($entity, $metadata->versionField);
|
||||
}
|
||||
|
||||
foreach ($metadata->fieldMappings as $name => $fieldMapping) {
|
||||
if (isset($fieldMapping['generated'])) {
|
||||
$data[$name] = $metadata->getFieldValue($entity, $name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($metadata->associationMappings as $name => $assoc) {
|
||||
|
||||
@@ -110,6 +110,34 @@ class FieldBuilder
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets insertable.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function insertable(bool $flag = true): self
|
||||
{
|
||||
if (! $flag) {
|
||||
$this->mapping['notInsertable'] = true;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets updatable.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function updatable(bool $flag = true): self
|
||||
{
|
||||
if (! $flag) {
|
||||
$this->mapping['notUpdatable'] = true;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets scale.
|
||||
*
|
||||
|
||||
@@ -80,6 +80,9 @@ use const PHP_VERSION_ID;
|
||||
* length?: int,
|
||||
* id?: bool,
|
||||
* nullable?: bool,
|
||||
* notInsertable?: bool,
|
||||
* notUpdatable?: bool,
|
||||
* generated?: string,
|
||||
* enumType?: class-string<BackedEnum>,
|
||||
* columnDefinition?: string,
|
||||
* precision?: int,
|
||||
@@ -258,6 +261,21 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
*/
|
||||
public const CACHE_USAGE_READ_WRITE = 3;
|
||||
|
||||
/**
|
||||
* The value of this column is never generated by the database.
|
||||
*/
|
||||
public const GENERATED_NEVER = 0;
|
||||
|
||||
/**
|
||||
* The value of this column is generated by the database on INSERT, but not on UPDATE.
|
||||
*/
|
||||
public const GENERATED_INSERT = 1;
|
||||
|
||||
/**
|
||||
* The value of this column is generated by the database on both INSERT and UDPATE statements.
|
||||
*/
|
||||
public const GENERATED_ALWAYS = 2;
|
||||
|
||||
/**
|
||||
* READ-ONLY: The name of the entity class.
|
||||
*
|
||||
@@ -439,6 +457,12 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
* - <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.
|
||||
*
|
||||
* - <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.
|
||||
*
|
||||
@@ -659,13 +683,21 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
*/
|
||||
public $changeTrackingPolicy = self::CHANGETRACKING_DEFERRED_IMPLICIT;
|
||||
|
||||
/**
|
||||
* READ-ONLY: A Flag indicating whether one or more columns of this class
|
||||
* have to be reloaded after insert / update operations.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $requiresFetchAfterChange = false;
|
||||
|
||||
/**
|
||||
* READ-ONLY: A flag for whether or not instances of this class are to be versioned
|
||||
* with optimistic locking.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $isVersioned;
|
||||
public $isVersioned = false;
|
||||
|
||||
/**
|
||||
* READ-ONLY: The name of the field which is used for versioning in optimistic locking (if any).
|
||||
@@ -963,6 +995,10 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
$serialized[] = 'cache';
|
||||
}
|
||||
|
||||
if ($this->requiresFetchAfterChange) {
|
||||
$serialized[] = 'requiresFetchAfterChange';
|
||||
}
|
||||
|
||||
return $serialized;
|
||||
}
|
||||
|
||||
@@ -1611,6 +1647,16 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
$mapping['requireSQLConversion'] = true;
|
||||
}
|
||||
|
||||
if (isset($mapping['generated'])) {
|
||||
if (! in_array($mapping['generated'], [self::GENERATED_NEVER, self::GENERATED_INSERT, self::GENERATED_ALWAYS])) {
|
||||
throw MappingException::invalidGeneratedMode($mapping['generated']);
|
||||
}
|
||||
|
||||
if ($mapping['generated'] === self::GENERATED_NEVER) {
|
||||
unset($mapping['generated']);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($mapping['enumType'])) {
|
||||
if (PHP_VERSION_ID < 80100) {
|
||||
throw MappingException::enumsRequirePhp81($this->name, $mapping['fieldName']);
|
||||
@@ -2675,6 +2721,10 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
$mapping = $this->validateAndCompleteFieldMapping($mapping);
|
||||
$this->assertFieldNotMapped($mapping['fieldName']);
|
||||
|
||||
if (isset($mapping['generated'])) {
|
||||
$this->requiresFetchAfterChange = true;
|
||||
}
|
||||
|
||||
$this->fieldMappings[$mapping['fieldName']] = $mapping;
|
||||
}
|
||||
|
||||
@@ -3405,8 +3455,9 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
*/
|
||||
public function setVersionMapping(array &$mapping)
|
||||
{
|
||||
$this->isVersioned = true;
|
||||
$this->versionField = $mapping['fieldName'];
|
||||
$this->isVersioned = true;
|
||||
$this->versionField = $mapping['fieldName'];
|
||||
$this->requiresFetchAfterChange = true;
|
||||
|
||||
if (! isset($mapping['default'])) {
|
||||
if (in_array($mapping['type'], ['integer', 'bigint', 'smallint'], true)) {
|
||||
@@ -3429,6 +3480,10 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
public function setVersioned($bool)
|
||||
{
|
||||
$this->isVersioned = $bool;
|
||||
|
||||
if ($bool) {
|
||||
$this->requiresFetchAfterChange = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -44,6 +44,12 @@ final class Column implements Annotation
|
||||
/** @var bool */
|
||||
public $nullable = false;
|
||||
|
||||
/** @var bool */
|
||||
public $insertable = true;
|
||||
|
||||
/** @var bool */
|
||||
public $updatable = true;
|
||||
|
||||
/** @var class-string<\BackedEnum>|null */
|
||||
public $enumType = null;
|
||||
|
||||
@@ -53,9 +59,17 @@ final class Column implements Annotation
|
||||
/** @var string|null */
|
||||
public $columnDefinition;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
* @psalm-var 'NEVER'|'INSERT'|'ALWAYS'|null
|
||||
* @Enum({"NEVER", "INSERT", "ALWAYS"})
|
||||
*/
|
||||
public $generated;
|
||||
|
||||
/**
|
||||
* @param class-string<\BackedEnum>|null $enumType
|
||||
* @param array<string,mixed> $options
|
||||
* @psalm-param 'NEVER'|'INSERT'|'ALWAYS'|null $generated
|
||||
*/
|
||||
public function __construct(
|
||||
?string $name = null,
|
||||
@@ -65,9 +79,12 @@ final class Column implements Annotation
|
||||
?int $scale = null,
|
||||
bool $unique = false,
|
||||
bool $nullable = false,
|
||||
bool $insertable = true,
|
||||
bool $updatable = true,
|
||||
?string $enumType = null,
|
||||
array $options = [],
|
||||
?string $columnDefinition = null
|
||||
?string $columnDefinition = null,
|
||||
?string $generated = null
|
||||
) {
|
||||
$this->name = $name;
|
||||
$this->type = $type;
|
||||
@@ -76,8 +93,11 @@ final class Column implements Annotation
|
||||
$this->scale = $scale;
|
||||
$this->unique = $unique;
|
||||
$this->nullable = $nullable;
|
||||
$this->insertable = $insertable;
|
||||
$this->updatable = $updatable;
|
||||
$this->enumType = $enumType;
|
||||
$this->options = $options;
|
||||
$this->columnDefinition = $columnDefinition;
|
||||
$this->generated = $generated;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -633,6 +633,22 @@ class AnnotationDriver extends AbstractAnnotationDriver
|
||||
return constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to resolve the generated mode.
|
||||
*
|
||||
* @psalm-return ClassMetadataInfo::GENERATED_*
|
||||
*
|
||||
* @throws MappingException If the fetch mode is not valid.
|
||||
*/
|
||||
private function getGeneratedMode(string $generatedMode): int
|
||||
{
|
||||
if (! defined('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode)) {
|
||||
throw MappingException::invalidGeneratedMode($generatedMode);
|
||||
}
|
||||
|
||||
return constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given method.
|
||||
*
|
||||
@@ -718,6 +734,9 @@ class AnnotationDriver extends AbstractAnnotationDriver
|
||||
* unique: bool,
|
||||
* nullable: bool,
|
||||
* precision: int,
|
||||
* notInsertable?: bool,
|
||||
* notUpdateble?: bool,
|
||||
* generated?: ClassMetadataInfo::GENERATED_*,
|
||||
* enumType?: class-string,
|
||||
* options?: mixed[],
|
||||
* columnName?: string,
|
||||
@@ -727,15 +746,27 @@ class AnnotationDriver extends AbstractAnnotationDriver
|
||||
private function columnToArray(string $fieldName, Mapping\Column $column): array
|
||||
{
|
||||
$mapping = [
|
||||
'fieldName' => $fieldName,
|
||||
'type' => $column->type,
|
||||
'scale' => $column->scale,
|
||||
'length' => $column->length,
|
||||
'unique' => $column->unique,
|
||||
'nullable' => $column->nullable,
|
||||
'precision' => $column->precision,
|
||||
'fieldName' => $fieldName,
|
||||
'type' => $column->type,
|
||||
'scale' => $column->scale,
|
||||
'length' => $column->length,
|
||||
'unique' => $column->unique,
|
||||
'nullable' => $column->nullable,
|
||||
'precision' => $column->precision,
|
||||
];
|
||||
|
||||
if (! $column->insertable) {
|
||||
$mapping['notInsertable'] = true;
|
||||
}
|
||||
|
||||
if (! $column->updatable) {
|
||||
$mapping['notUpdatable'] = true;
|
||||
}
|
||||
|
||||
if ($column->generated) {
|
||||
$mapping['generated'] = $this->getGeneratedMode($column->generated);
|
||||
}
|
||||
|
||||
if ($column->options) {
|
||||
$mapping['options'] = $column->options;
|
||||
}
|
||||
|
||||
@@ -528,6 +528,20 @@ class AttributeDriver extends AnnotationDriver
|
||||
return constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to resolve the generated mode.
|
||||
*
|
||||
* @throws MappingException If the fetch mode is not valid.
|
||||
*/
|
||||
private function getGeneratedMode(string $generatedMode): int
|
||||
{
|
||||
if (! defined('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode)) {
|
||||
throw MappingException::invalidGeneratedMode($generatedMode);
|
||||
}
|
||||
|
||||
return constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given method.
|
||||
*
|
||||
@@ -644,6 +658,18 @@ class AttributeDriver extends AnnotationDriver
|
||||
$mapping['columnDefinition'] = $column->columnDefinition;
|
||||
}
|
||||
|
||||
if ($column->updatable === false) {
|
||||
$mapping['notUpdatable'] = true;
|
||||
}
|
||||
|
||||
if ($column->insertable === false) {
|
||||
$mapping['notInsertable'] = true;
|
||||
}
|
||||
|
||||
if ($column->generated !== null) {
|
||||
$mapping['generated'] = $this->getGeneratedMode($column->generated);
|
||||
}
|
||||
|
||||
if ($column->enumType) {
|
||||
$mapping['enumType'] = $column->enumType;
|
||||
}
|
||||
|
||||
@@ -800,6 +800,8 @@ class XmlDriver extends FileDriver
|
||||
* scale?: int,
|
||||
* unique?: bool,
|
||||
* nullable?: bool,
|
||||
* notInsertable?: bool,
|
||||
* notUpdatable?: bool,
|
||||
* enumType?: string,
|
||||
* version?: bool,
|
||||
* columnDefinition?: string,
|
||||
@@ -840,6 +842,18 @@ class XmlDriver extends FileDriver
|
||||
$mapping['nullable'] = $this->evaluateBoolean($fieldMapping['nullable']);
|
||||
}
|
||||
|
||||
if (isset($fieldMapping['insertable']) && ! $this->evaluateBoolean($fieldMapping['insertable'])) {
|
||||
$mapping['notInsertable'] = true;
|
||||
}
|
||||
|
||||
if (isset($fieldMapping['updatable']) && ! $this->evaluateBoolean($fieldMapping['updatable'])) {
|
||||
$mapping['notUpdatable'] = true;
|
||||
}
|
||||
|
||||
if (isset($fieldMapping['generated'])) {
|
||||
$mapping['generated'] = constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . (string) $fieldMapping['generated']);
|
||||
}
|
||||
|
||||
if (isset($fieldMapping['version']) && $fieldMapping['version']) {
|
||||
$mapping['version'] = $this->evaluateBoolean($fieldMapping['version']);
|
||||
}
|
||||
|
||||
@@ -786,6 +786,9 @@ class YamlDriver extends FileDriver
|
||||
* unique?: mixed,
|
||||
* options?: mixed,
|
||||
* nullable?: mixed,
|
||||
* insertable?: mixed,
|
||||
* updatable?: mixed,
|
||||
* generated?: mixed,
|
||||
* enumType?: class-string,
|
||||
* version?: mixed,
|
||||
* columnDefinition?: mixed
|
||||
@@ -802,6 +805,9 @@ class YamlDriver extends FileDriver
|
||||
* unique?: bool,
|
||||
* options?: mixed,
|
||||
* nullable?: mixed,
|
||||
* notInsertable?: mixed,
|
||||
* notUpdatable?: mixed,
|
||||
* generated?: mixed,
|
||||
* enumType?: class-string,
|
||||
* version?: mixed,
|
||||
* columnDefinition?: mixed
|
||||
@@ -850,6 +856,18 @@ class YamlDriver extends FileDriver
|
||||
$mapping['nullable'] = $column['nullable'];
|
||||
}
|
||||
|
||||
if (isset($column['insertable']) && ! (bool) $column['insertable']) {
|
||||
$mapping['notInsertable'] = true;
|
||||
}
|
||||
|
||||
if (isset($column['updatable']) && ! (bool) $column['updatable']) {
|
||||
$mapping['notUpdatable'] = true;
|
||||
}
|
||||
|
||||
if (isset($column['generated'])) {
|
||||
$mapping['generated'] = constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $column['generated']);
|
||||
}
|
||||
|
||||
if (isset($column['version']) && $column['version']) {
|
||||
$mapping['version'] = $column['version'];
|
||||
}
|
||||
|
||||
@@ -825,6 +825,11 @@ class MappingException extends ORMException
|
||||
return new self("Entity '" . $className . "' has a mapping with invalid fetch mode '" . $annotation . "'");
|
||||
}
|
||||
|
||||
public static function invalidGeneratedMode(string $annotation): MappingException
|
||||
{
|
||||
return new self("Invalid generated mode '" . $annotation . "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
*
|
||||
|
||||
@@ -30,8 +30,10 @@ use Doctrine\ORM\Repository\Exception\InvalidFindByCall;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
use Doctrine\ORM\Utility\IdentifierFlattener;
|
||||
use Doctrine\ORM\Utility\PersisterHelper;
|
||||
use LengthException;
|
||||
|
||||
use function array_combine;
|
||||
use function array_keys;
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function array_search;
|
||||
@@ -284,8 +286,8 @@ class BasicEntityPersister implements EntityPersister
|
||||
$id = $this->class->getIdentifierValues($entity);
|
||||
}
|
||||
|
||||
if ($this->class->isVersioned) {
|
||||
$this->assignDefaultVersionValue($entity, $id);
|
||||
if ($this->class->requiresFetchAfterChange) {
|
||||
$this->assignDefaultVersionAndUpsertableValues($entity, $id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,50 +299,71 @@ class BasicEntityPersister implements EntityPersister
|
||||
/**
|
||||
* Retrieves the default version value which was created
|
||||
* by the preceding INSERT statement and assigns it back in to the
|
||||
* entities version field.
|
||||
* entities version field if the given entity is versioned.
|
||||
* Also retrieves values of columns marked as 'non insertable' and / or
|
||||
* 'not updatable' and assigns them back to the entities corresponding fields.
|
||||
*
|
||||
* @param object $entity
|
||||
* @param mixed[] $id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function assignDefaultVersionValue($entity, array $id)
|
||||
protected function assignDefaultVersionAndUpsertableValues($entity, array $id)
|
||||
{
|
||||
$value = $this->fetchVersionValue($this->class, $id);
|
||||
$values = $this->fetchVersionAndNotUpsertableValues($this->class, $id);
|
||||
|
||||
$this->class->setFieldValue($entity, $this->class->versionField, $value);
|
||||
foreach ($values as $field => $value) {
|
||||
$value = Type::getType($this->class->fieldMappings[$field]['type'])->convertToPHPValue($value, $this->platform);
|
||||
|
||||
$this->class->setFieldValue($entity, $field, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the current version value of a versioned entity.
|
||||
* Fetches the current version value of a versioned entity and / or the values of fields
|
||||
* marked as 'not insertable' and / or 'not updatable'.
|
||||
*
|
||||
* @param ClassMetadata $versionedClass
|
||||
* @param mixed[] $id
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function fetchVersionValue($versionedClass, array $id)
|
||||
protected function fetchVersionAndNotUpsertableValues($versionedClass, array $id)
|
||||
{
|
||||
$versionField = $versionedClass->versionField;
|
||||
$fieldMapping = $versionedClass->fieldMappings[$versionField];
|
||||
$tableName = $this->quoteStrategy->getTableName($versionedClass, $this->platform);
|
||||
$identifier = $this->quoteStrategy->getIdentifierColumnNames($versionedClass, $this->platform);
|
||||
$columnName = $this->quoteStrategy->getColumnName($versionField, $versionedClass, $this->platform);
|
||||
$columnNames = [];
|
||||
foreach ($this->class->fieldMappings as $key => $column) {
|
||||
if (isset($column['generated']) || ($this->class->isVersioned && $key === $versionedClass->versionField)) {
|
||||
$columnNames[$key] = $this->quoteStrategy->getColumnName($key, $versionedClass, $this->platform);
|
||||
}
|
||||
}
|
||||
|
||||
$tableName = $this->quoteStrategy->getTableName($versionedClass, $this->platform);
|
||||
$identifier = $this->quoteStrategy->getIdentifierColumnNames($versionedClass, $this->platform);
|
||||
|
||||
// FIXME: Order with composite keys might not be correct
|
||||
$sql = 'SELECT ' . $columnName
|
||||
. ' FROM ' . $tableName
|
||||
. ' WHERE ' . implode(' = ? AND ', $identifier) . ' = ?';
|
||||
$sql = 'SELECT ' . implode(', ', $columnNames)
|
||||
. ' FROM ' . $tableName
|
||||
. ' WHERE ' . implode(' = ? AND ', $identifier) . ' = ?';
|
||||
|
||||
$flatId = $this->identifierFlattener->flattenIdentifier($versionedClass, $id);
|
||||
|
||||
$value = $this->conn->fetchOne(
|
||||
$values = $this->conn->fetchNumeric(
|
||||
$sql,
|
||||
array_values($flatId),
|
||||
$this->extractIdentifierTypes($id, $versionedClass)
|
||||
);
|
||||
|
||||
return Type::getType($fieldMapping['type'])->convertToPHPValue($value, $this->platform);
|
||||
if ($values === false) {
|
||||
throw new LengthException('Unexpected empty result for database query.');
|
||||
}
|
||||
|
||||
$values = array_combine(array_keys($columnNames), $values);
|
||||
|
||||
if (! $values) {
|
||||
throw new LengthException('Unexpected number of database columns.');
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -383,10 +406,10 @@ class BasicEntityPersister implements EntityPersister
|
||||
|
||||
$this->updateTable($entity, $quotedTableName, $data, $isVersioned);
|
||||
|
||||
if ($isVersioned) {
|
||||
if ($this->class->requiresFetchAfterChange) {
|
||||
$id = $this->class->getIdentifierValues($entity);
|
||||
|
||||
$this->assignDefaultVersionValue($entity, $id);
|
||||
$this->assignDefaultVersionAndUpsertableValues($entity, $id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,12 +617,13 @@ class BasicEntityPersister implements EntityPersister
|
||||
* )
|
||||
* </code>
|
||||
*
|
||||
* @param object $entity The entity for which to prepare the data.
|
||||
* @param object $entity The entity for which to prepare the data.
|
||||
* @param bool $isInsert Whether the data to be prepared refers to an insert statement.
|
||||
*
|
||||
* @return mixed[][] The prepared data.
|
||||
* @psalm-return array<string, array<array-key, mixed|null>>
|
||||
*/
|
||||
protected function prepareUpdateData($entity)
|
||||
protected function prepareUpdateData($entity, bool $isInsert = false)
|
||||
{
|
||||
$versionField = null;
|
||||
$result = [];
|
||||
@@ -625,6 +649,14 @@ class BasicEntityPersister implements EntityPersister
|
||||
$fieldMapping = $this->class->fieldMappings[$field];
|
||||
$columnName = $fieldMapping['columnName'];
|
||||
|
||||
if (! $isInsert && isset($fieldMapping['notUpdatable'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($isInsert && isset($fieldMapping['notInsertable'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->columnTypes[$columnName] = $fieldMapping['type'];
|
||||
|
||||
$result[$this->getOwningTable($field)][$columnName] = $newVal;
|
||||
@@ -692,7 +724,7 @@ class BasicEntityPersister implements EntityPersister
|
||||
*/
|
||||
protected function prepareInsertData($entity)
|
||||
{
|
||||
return $this->prepareUpdateData($entity);
|
||||
return $this->prepareUpdateData($entity, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1440,6 +1472,10 @@ class BasicEntityPersister implements EntityPersister
|
||||
}
|
||||
|
||||
if (! $this->class->isIdGeneratorIdentity() || $this->class->identifier[0] !== $name) {
|
||||
if (isset($this->class->fieldMappings[$name]['notInsertable'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$columns[] = $this->quoteStrategy->getColumnName($name, $this->class, $this->platform);
|
||||
$this->columnTypes[$name] = $this->class->fieldMappings[$name]['type'];
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Doctrine\ORM\Persisters\Entity;
|
||||
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Internal\SQLResultCasing;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
@@ -168,8 +169,8 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
$id = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
|
||||
}
|
||||
|
||||
if ($this->class->isVersioned) {
|
||||
$this->assignDefaultVersionValue($entity, $id);
|
||||
if ($this->class->requiresFetchAfterChange) {
|
||||
$this->assignDefaultVersionAndUpsertableValues($entity, $id);
|
||||
}
|
||||
|
||||
// Execute inserts on subtables.
|
||||
@@ -211,9 +212,6 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
}
|
||||
|
||||
$isVersioned = $this->class->isVersioned;
|
||||
if ($isVersioned === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$versionedClass = $this->getVersionedClassMetadata();
|
||||
$versionedTable = $versionedClass->getTableName();
|
||||
@@ -225,10 +223,10 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
$this->updateTable($entity, $tableName, $data, $versioned);
|
||||
}
|
||||
|
||||
// Make sure the table with the version column is updated even if no columns on that
|
||||
// table were affected.
|
||||
if ($isVersioned) {
|
||||
if (! isset($updateData[$versionedTable])) {
|
||||
if ($this->class->requiresFetchAfterChange) {
|
||||
// Make sure the table with the version column is updated even if no columns on that
|
||||
// table were affected.
|
||||
if ($isVersioned && ! isset($updateData[$versionedTable])) {
|
||||
$tableName = $this->quoteStrategy->getTableName($versionedClass, $this->platform);
|
||||
|
||||
$this->updateTable($entity, $tableName, [], true);
|
||||
@@ -236,7 +234,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
|
||||
$identifiers = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
|
||||
|
||||
$this->assignDefaultVersionValue($entity, $identifiers);
|
||||
$this->assignDefaultVersionAndUpsertableValues($entity, $identifiers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,10 +547,15 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function assignDefaultVersionValue($entity, array $id)
|
||||
protected function assignDefaultVersionAndUpsertableValues($entity, array $id)
|
||||
{
|
||||
$value = $this->fetchVersionValue($this->getVersionedClassMetadata(), $id);
|
||||
$this->class->setFieldValue($entity, $this->class->versionField, $value);
|
||||
$values = $this->fetchVersionAndNotUpsertableValues($this->getVersionedClassMetadata(), $id);
|
||||
|
||||
foreach ($values as $field => $value) {
|
||||
$value = Type::getType($this->class->fieldMappings[$field]['type'])->convertToPHPValue($value, $this->platform);
|
||||
|
||||
$this->class->setFieldValue($entity, $field, $value);
|
||||
}
|
||||
}
|
||||
|
||||
private function getJoinSql(string $baseTableAlias): string
|
||||
|
||||
@@ -215,6 +215,14 @@ class XmlExporter extends AbstractExporter
|
||||
if (isset($field['nullable'])) {
|
||||
$fieldXml->addAttribute('nullable', $field['nullable'] ? 'true' : 'false');
|
||||
}
|
||||
|
||||
if (isset($field['notInsertable'])) {
|
||||
$fieldXml->addAttribute('insertable', 'false');
|
||||
}
|
||||
|
||||
if (isset($field['notUpdatable'])) {
|
||||
$fieldXml->addAttribute('updatable', 'false');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -760,16 +760,6 @@ parameters:
|
||||
count: 1
|
||||
path: lib/Doctrine/ORM/Persisters/Entity/CachedPersisterContext.php
|
||||
|
||||
-
|
||||
message: "#^If condition is always true\\.$#"
|
||||
count: 1
|
||||
path: lib/Doctrine/ORM/Persisters/Entity/JoinedSubclassPersister.php
|
||||
|
||||
-
|
||||
message: "#^Left side of && is always true\\.$#"
|
||||
count: 1
|
||||
path: lib/Doctrine/ORM/Persisters/Entity/JoinedSubclassPersister.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$em of method Doctrine\\\\ORM\\\\Id\\\\AbstractIdGenerator\\:\\:generate\\(\\) expects Doctrine\\\\ORM\\\\EntityManager, Doctrine\\\\ORM\\\\EntityManagerInterface given\\.$#"
|
||||
count: 1
|
||||
@@ -1741,7 +1731,7 @@ parameters:
|
||||
path: lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php
|
||||
|
||||
-
|
||||
message: "#^Offset 'version' on array\\{type\\: string, fieldName\\: string, columnName\\: string, length\\?\\: int, id\\?\\: bool, nullable\\?\\: bool, enumType\\?\\: class\\-string\\<BackedEnum\\>, columnDefinition\\?\\: string, \\.\\.\\.\\} in isset\\(\\) does not exist\\.$#"
|
||||
message: "#^Offset 'version' on array\\{type\\: string, fieldName\\: string, columnName\\: string, length\\?\\: int, id\\?\\: bool, nullable\\?\\: bool, notInsertable\\?\\: bool, notUpdatable\\?\\: bool, \\.\\.\\.\\} in isset\\(\\) does not exist\\.$#"
|
||||
count: 1
|
||||
path: lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php
|
||||
|
||||
|
||||
74
tests/Doctrine/Tests/Models/Upsertable/Insertable.php
Normal file
74
tests/Doctrine/Tests/Models/Upsertable/Insertable.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\Upsertable;
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\Table;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @Table(name="insertable_column")
|
||||
*/
|
||||
#[Entity]
|
||||
#[Table(name: 'insertable_column')]
|
||||
class Insertable
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
* @Id
|
||||
* @GeneratedValue
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
#[Id, GeneratedValue, Column(type: 'integer')]
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @Column(type="string", insertable=false, options={"default": "1234"}, generated="INSERT")
|
||||
*/
|
||||
#[Column(type: 'string', insertable: false, options: ['default' => '1234'], generated: 'INSERT')]
|
||||
public $nonInsertableContent;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @Column(type="string", insertable=true)
|
||||
*/
|
||||
#[Column(type: 'string', insertable: true)]
|
||||
public $insertableContent;
|
||||
|
||||
public static function loadMetadata(ClassMetadata $metadata): ClassMetadata
|
||||
{
|
||||
$metadata->setPrimaryTable(
|
||||
['name' => 'insertable_column']
|
||||
);
|
||||
|
||||
$metadata->mapField(
|
||||
[
|
||||
'id' => true,
|
||||
'fieldName' => 'id',
|
||||
]
|
||||
);
|
||||
$metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_AUTO);
|
||||
|
||||
$metadata->mapField(
|
||||
[
|
||||
'fieldName' => 'nonInsertableContent',
|
||||
'notInsertable' => true,
|
||||
'options' => ['default' => '1234'],
|
||||
'generated' => ClassMetadataInfo::GENERATED_INSERT,
|
||||
]
|
||||
);
|
||||
$metadata->mapField(
|
||||
['fieldName' => 'insertableContent']
|
||||
);
|
||||
|
||||
return $metadata;
|
||||
}
|
||||
}
|
||||
72
tests/Doctrine/Tests/Models/Upsertable/Updatable.php
Normal file
72
tests/Doctrine/Tests/Models/Upsertable/Updatable.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\Upsertable;
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\Table;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @Table(name="updatable_column")
|
||||
*/
|
||||
#[Entity, Table(name: 'updatable_column')]
|
||||
class Updatable
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
* @Id
|
||||
* @GeneratedValue
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
#[Id, GeneratedValue, Column(type: 'integer')]
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @Column(type="string", name="non_updatable_content", updatable=false, generated="ALWAYS")
|
||||
*/
|
||||
#[Column(type: 'string', name: 'non_updatable_content', updatable: false, generated: 'ALWAYS')]
|
||||
public $nonUpdatableContent;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @Column(type="string", updatable=true)
|
||||
*/
|
||||
#[Column(type: 'string', updatable: true)]
|
||||
public $updatableContent;
|
||||
|
||||
public static function loadMetadata(ClassMetadata $metadata): ClassMetadata
|
||||
{
|
||||
$metadata->setPrimaryTable(
|
||||
['name' => 'updatable_column']
|
||||
);
|
||||
|
||||
$metadata->mapField(
|
||||
[
|
||||
'id' => true,
|
||||
'fieldName' => 'id',
|
||||
]
|
||||
);
|
||||
$metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_AUTO);
|
||||
|
||||
$metadata->mapField(
|
||||
[
|
||||
'fieldName' => 'nonUpdatableContent',
|
||||
'notUpdatable' => true,
|
||||
'generated' => ClassMetadataInfo::GENERATED_ALWAYS,
|
||||
]
|
||||
);
|
||||
$metadata->mapField(
|
||||
['fieldName' => 'updatableContent']
|
||||
);
|
||||
|
||||
return $metadata;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\ORM\Tools\ToolsException;
|
||||
use Doctrine\Tests\Models\Upsertable\Insertable;
|
||||
use Doctrine\Tests\Models\Upsertable\Updatable;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
class InsertableUpdatableTest extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
try {
|
||||
$this->_schemaTool->createSchema(
|
||||
[
|
||||
$this->_em->getClassMetadata(Updatable::class),
|
||||
$this->_em->getClassMetadata(Insertable::class),
|
||||
]
|
||||
);
|
||||
} catch (ToolsException $e) {
|
||||
}
|
||||
}
|
||||
|
||||
public function testNotInsertableIsFetchedFromDatabase(): void
|
||||
{
|
||||
$insertable = new Insertable();
|
||||
$insertable->insertableContent = 'abcdefg';
|
||||
|
||||
$this->_em->persist($insertable);
|
||||
$this->_em->flush();
|
||||
|
||||
// gets inserted from default value and fetches value from database
|
||||
self::assertEquals('1234', $insertable->nonInsertableContent);
|
||||
|
||||
$insertable->nonInsertableContent = '5678';
|
||||
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$insertable = $this->_em->find(Insertable::class, $insertable->id);
|
||||
|
||||
// during UPDATE statement it is not ignored
|
||||
self::assertEquals('5678', $insertable->nonInsertableContent);
|
||||
}
|
||||
|
||||
public function testNotUpdatableIsFetched(): void
|
||||
{
|
||||
$updatable = new Updatable();
|
||||
$updatable->updatableContent = 'foo';
|
||||
$updatable->nonUpdatableContent = 'foo';
|
||||
|
||||
$this->_em->persist($updatable);
|
||||
$this->_em->flush();
|
||||
|
||||
$updatable->updatableContent = 'bar';
|
||||
$updatable->nonUpdatableContent = 'baz';
|
||||
|
||||
$this->_em->flush();
|
||||
|
||||
self::assertEquals('foo', $updatable->nonUpdatableContent);
|
||||
|
||||
$this->_em->clear();
|
||||
|
||||
$cleanUpdatable = $this->_em->find(Updatable::class, $updatable->id);
|
||||
|
||||
self::assertEquals('bar', $cleanUpdatable->updatableContent);
|
||||
self::assertEquals('foo', $cleanUpdatable->nonUpdatableContent);
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,8 @@ use Doctrine\Tests\Models\Enums\Card;
|
||||
use Doctrine\Tests\Models\Enums\Suit;
|
||||
use Doctrine\Tests\Models\TypedProperties\Contact;
|
||||
use Doctrine\Tests\Models\TypedProperties\UserTyped;
|
||||
use Doctrine\Tests\Models\Upsertable\Insertable;
|
||||
use Doctrine\Tests\Models\Upsertable\Updatable;
|
||||
use Doctrine\Tests\OrmTestCase;
|
||||
|
||||
use function assert;
|
||||
@@ -1145,6 +1147,30 @@ abstract class AbstractMappingDriverTest extends OrmTestCase
|
||||
self::assertSame('count', $metadata->getFieldMapping('count')['columnName']);
|
||||
}
|
||||
|
||||
public function testInsertableColumn(): void
|
||||
{
|
||||
$metadata = $this->createClassMetadata(Insertable::class);
|
||||
|
||||
$mapping = $metadata->getFieldMapping('nonInsertableContent');
|
||||
|
||||
self::assertArrayHasKey('notInsertable', $mapping);
|
||||
self::assertArrayHasKey('generated', $mapping);
|
||||
self::assertSame(ClassMetadataInfo::GENERATED_INSERT, $mapping['generated']);
|
||||
self::assertArrayNotHasKey('notInsertable', $metadata->getFieldMapping('insertableContent'));
|
||||
}
|
||||
|
||||
public function testUpdatableColumn(): void
|
||||
{
|
||||
$metadata = $this->createClassMetadata(Updatable::class);
|
||||
|
||||
$mapping = $metadata->getFieldMapping('nonUpdatableContent');
|
||||
|
||||
self::assertArrayHasKey('notUpdatable', $mapping);
|
||||
self::assertArrayHasKey('generated', $mapping);
|
||||
self::assertSame(ClassMetadataInfo::GENERATED_ALWAYS, $mapping['generated']);
|
||||
self::assertArrayNotHasKey('notUpdatable', $metadata->getFieldMapping('updatableContent'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires PHP 8.1
|
||||
*/
|
||||
|
||||
@@ -67,7 +67,10 @@ class ClassMetadataTest extends OrmTestCase
|
||||
$cm->setDiscriminatorColumn(['name' => 'disc', 'type' => 'integer']);
|
||||
$cm->mapOneToOne(['fieldName' => 'phonenumbers', 'targetEntity' => 'CmsAddress', 'mappedBy' => 'foo']);
|
||||
$cm->markReadOnly();
|
||||
$cm->mapField(['fieldName' => 'status', 'notInsertable' => true, 'generated' => ClassMetadata::GENERATED_ALWAYS]);
|
||||
$cm->addNamedQuery(['name' => 'dql', 'query' => 'foo']);
|
||||
|
||||
self::assertTrue($cm->requiresFetchAfterChange);
|
||||
self::assertEquals(1, count($cm->associationMappings));
|
||||
|
||||
$serialized = serialize($cm);
|
||||
@@ -92,6 +95,7 @@ class ClassMetadataTest extends OrmTestCase
|
||||
self::assertEquals(CMS\CmsAddress::class, $oneOneMapping['targetEntity']);
|
||||
self::assertTrue($cm->isReadOnly);
|
||||
self::assertEquals(['dql' => ['name' => 'dql', 'query' => 'foo', 'dql' => 'foo']], $cm->namedQueries);
|
||||
self::assertTrue($cm->requiresFetchAfterChange);
|
||||
}
|
||||
|
||||
public function testFieldIsNullable(): void
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||
|
||||
$metadata->setPrimaryTable(
|
||||
['name' => 'insertable_column']
|
||||
);
|
||||
|
||||
$metadata->mapField(
|
||||
[
|
||||
'id' => true,
|
||||
'fieldName' => 'id',
|
||||
]
|
||||
);
|
||||
$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO);
|
||||
|
||||
$metadata->mapField(
|
||||
[
|
||||
'fieldName' => 'nonInsertableContent',
|
||||
'notInsertable' => true,
|
||||
'options' => ['default' => '1234'],
|
||||
'generated' => ClassMetadataInfo::GENERATED_INSERT,
|
||||
]
|
||||
);
|
||||
$metadata->mapField(
|
||||
['fieldName' => 'insertableContent']
|
||||
);
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||
|
||||
$metadata->setPrimaryTable(
|
||||
['name' => 'updatable_column']
|
||||
);
|
||||
|
||||
$metadata->mapField(
|
||||
[
|
||||
'id' => true,
|
||||
'fieldName' => 'id',
|
||||
]
|
||||
);
|
||||
$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO);
|
||||
|
||||
$metadata->mapField(
|
||||
[
|
||||
'fieldName' => 'nonUpdatableContent',
|
||||
'notUpdatable' => true,
|
||||
'generated' => ClassMetadataInfo::GENERATED_ALWAYS,
|
||||
]
|
||||
);
|
||||
$metadata->mapField(
|
||||
['fieldName' => 'updatableContent']
|
||||
);
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Doctrine\Tests\Models\Upsertable\Insertable" table="insertable_column">
|
||||
<id name="id">
|
||||
<generator strategy="AUTO"/>
|
||||
</id>
|
||||
|
||||
<field name="nonInsertableContent" insertable="false" type="string" generated="INSERT" />
|
||||
<field name="insertableContent" insertable="true" type="string" />
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Doctrine\Tests\Models\Upsertable\Updatable" table="updatable_column">
|
||||
<id name="id">
|
||||
<generator strategy="AUTO"/>
|
||||
</id>
|
||||
|
||||
<field name="nonUpdatableContent" updatable="false" type="string" generated="ALWAYS" />
|
||||
<field name="updatableContent" updatable="true" type="string" />
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
@@ -0,0 +1,17 @@
|
||||
Doctrine\Tests\Models\Upsertable\Insertable:
|
||||
type: entity
|
||||
table: insertable_column
|
||||
id:
|
||||
id:
|
||||
generator:
|
||||
strategy: AUTO
|
||||
fields:
|
||||
nonInsertableContent:
|
||||
type: string
|
||||
insertable: false
|
||||
generated: INSERT
|
||||
options:
|
||||
default: 1234
|
||||
insertableContent:
|
||||
type: string
|
||||
insertable: true
|
||||
@@ -0,0 +1,17 @@
|
||||
Doctrine\Tests\Models\Upsertable\Updatable:
|
||||
type: entity
|
||||
table: updatable_column
|
||||
id:
|
||||
id:
|
||||
generator:
|
||||
strategy: AUTO
|
||||
fields:
|
||||
nonUpdatableContent:
|
||||
type: string
|
||||
updatable: false
|
||||
generated: ALWAYS
|
||||
options:
|
||||
default: 1234
|
||||
updatableContent:
|
||||
type: string
|
||||
updatable: true
|
||||
Reference in New Issue
Block a user