Skip to content

Commit

Permalink
Generated/Virtual Columns: Insertable / Updateable
Browse files Browse the repository at this point in the history
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.
doctrine#5728
  • Loading branch information
mehldau committed Nov 9, 2021
1 parent 1e971d8 commit 228e907
Show file tree
Hide file tree
Showing 13 changed files with 331 additions and 7 deletions.
4 changes: 4 additions & 0 deletions doctrine-mapping.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,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" />
Expand Down Expand Up @@ -616,6 +618,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" />
Expand Down
28 changes: 28 additions & 0 deletions lib/Doctrine/ORM/Mapping/Builder/FieldBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,34 @@ public function precision($p)
return $this;
}

/**
* Sets insertable.
*
* @param bool $flag
*
* @return $this
*/
public function insertable($flag = true)
{
$this->mapping['insertable'] = (bool) $flag;

return $this;
}

/**
* Sets updateable.
*
* @param bool $flag
*
* @return $this
*/
public function updateable($flag = true)
{
$this->mapping['updateable'] = (bool) $flag;

return $this;
}

/**
* Sets scale.
*
Expand Down
38 changes: 38 additions & 0 deletions lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,12 @@ class ClassMetadataInfo implements ClassMetadata
* - <b>nullable</b> (boolean, optional)
* Whether the column is nullable. Defaults to FALSE.
*
* - <b>'insertable'</b> (boolean, optional)
* Whether the column is insertable. Defaults to TRUE.
*
* - <b>'updateable'</b> (boolean, optional)
* Whether the column is updateable. Defaults to TRUE.
*
* - <b>columnDefinition</b> (string, optional, schema-only)
* The SQL fragment that is used when generating the DDL for the column.
*
Expand All @@ -433,6 +439,8 @@ class ClassMetadataInfo implements ClassMetadata
* length?: int,
* id?: bool,
* nullable?: bool,
* insertable?: bool,
* updateable?: bool,
* columnDefinition?: string,
* precision?: int,
* scale?: int,
Expand Down Expand Up @@ -1255,6 +1263,34 @@ public function isNullable($fieldName)
return $mapping !== false && isset($mapping['nullable']) && $mapping['nullable'];
}

/**
* Checks if the field is insertable.
*
* @param string $fieldName The field name.
*
* @return bool TRUE if the field is not null, FALSE otherwise.
*/
public function isInsertable($fieldName)
{
$mapping = $this->getFieldMapping($fieldName);

return $mapping !== false && isset($mapping['insertable']) && $mapping['insertable'];
}

/**
* Checks if the field is updateable.
*
* @param string $fieldName The field name.
*
* @return bool TRUE if the field is not null, FALSE otherwise.
*/
public function isUpdateable($fieldName)
{
$mapping = $this->getFieldMapping($fieldName);

return $mapping !== false && isset($mapping['updateable']) && $mapping['updateable'];
}

/**
* Gets a column name for a field name.
* If the column name for the field cannot be found, the given field name
Expand Down Expand Up @@ -1282,6 +1318,8 @@ public function getColumnName($fieldName)
* columnName?: string,
* inherited?: class-string,
* nullable?: bool,
* insertable?: bool,
* updateable?: bool,
* originalClass?: class-string,
* originalField?: string,
* scale?: int,
Expand Down
10 changes: 10 additions & 0 deletions lib/Doctrine/ORM/Mapping/Column.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ final class Column implements Annotation
/** @var bool */
public $nullable = false;

/** @var bool */
public $insertable = true;

/** @var bool */
public $updateable = true;

/** @var array<string,mixed> */
public $options = [];

Expand All @@ -61,6 +67,8 @@ public function __construct(
?int $scale = null,
bool $unique = false,
bool $nullable = false,
?bool $insertable = true,
?bool $updateable = true,
array $options = [],
?string $columnDefinition = null
) {
Expand All @@ -71,6 +79,8 @@ public function __construct(
$this->scale = $scale;
$this->unique = $unique;
$this->nullable = $nullable;
$this->insertable = $insertable;
$this->updateable = $updateable;
$this->options = $options;
$this->columnDefinition = $columnDefinition;
}
Expand Down
24 changes: 17 additions & 7 deletions lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,8 @@ private function joinColumnToArray(Mapping\JoinColumn $joinColumn): array
* unique: bool,
* nullable: bool,
* precision: int,
* insertable?: bool,
* updateble?: bool,
* options?: mixed[],
* columnName?: string,
* columnDefinition?: string
Expand All @@ -728,15 +730,23 @@ private function joinColumnToArray(Mapping\JoinColumn $joinColumn): array
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['insertable'] = $column->insertable;
}

if ($column->updateable) {
$mapping['updateable'] = $column->updateable;
}

if ($column->options) {
$mapping['options'] = $column->options;
}
Expand Down
10 changes: 10 additions & 0 deletions lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,8 @@ private function joinColumnToArray(SimpleXMLElement $joinColumnElement): array
* scale?: int,
* unique?: bool,
* nullable?: bool,
* insertable?: bool,
* updateable?: bool,
* version?: bool,
* columnDefinition?: string,
* options?: array
Expand Down Expand Up @@ -839,6 +841,14 @@ private function columnToArray(SimpleXMLElement $fieldMapping): array
$mapping['nullable'] = $this->evaluateBoolean($fieldMapping['nullable']);
}

if (isset($fieldMapping['insertable'])) {
$mapping['insertable'] = $this->evaluateBoolean($fieldMapping['insertable']);
}

if (isset($fieldMapping['updateable'])) {
$mapping['updateable'] = $this->evaluateBoolean($fieldMapping['updateable']);
}

if (isset($fieldMapping['version']) && $fieldMapping['version']) {
$mapping['version'] = $this->evaluateBoolean($fieldMapping['version']);
}
Expand Down
12 changes: 12 additions & 0 deletions lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,8 @@ private function joinColumnToArray(array $joinColumnElement): array
* unique?: mixed,
* options?: mixed,
* nullable?: mixed,
* insertable?: mixed,
* updateable?: mixed,
* version?: mixed,
* columnDefinition?: mixed
* }|null $column
Expand All @@ -801,6 +803,8 @@ private function joinColumnToArray(array $joinColumnElement): array
* unique?: bool,
* options?: mixed,
* nullable?: mixed,
* insertable?: mixed,
* updateable?: mixed,
* version?: mixed,
* columnDefinition?: mixed
* }
Expand Down Expand Up @@ -848,6 +852,14 @@ private function columnToArray(string $fieldName, ?array $column): array
$mapping['nullable'] = $column['nullable'];
}

if (isset($column['insertable'])) {
$mapping['insertable'] = $column['insertable'];
}

if (isset($column['updateable'])) {
$mapping['updateable'] = $column['updateable'];
}

if (isset($column['version']) && $column['version']) {
$mapping['version'] = $column['version'];
}
Expand Down
18 changes: 18 additions & 0 deletions lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Persisters\Exception\CantUseInOperatorOnCompositeKeys;
use Doctrine\ORM\Persisters\Exception\InvalidOrientation;
use Doctrine\ORM\Persisters\Exception\NonUpdateableField;
use Doctrine\ORM\Persisters\Exception\UnrecognizedField;
use Doctrine\ORM\Persisters\SqlExpressionVisitor;
use Doctrine\ORM\Persisters\SqlValueVisitor;
Expand Down Expand Up @@ -627,6 +628,19 @@ protected function prepareUpdateData($entity)
$fieldMapping = $this->class->fieldMappings[$field];
$columnName = $fieldMapping['columnName'];

$isInsert = ! empty($this->queuedInserts[spl_object_id($entity)]);
if (! $isInsert && ! $this->class->isUpdateable($field)) {
if ($change[0] !== $change[1]) {
throw NonUpdateableField::byName($field);
}

continue;
}

if ($isInsert && ! $this->class->isInsertable($field)) {
continue;
}

$this->columnTypes[$columnName] = $fieldMapping['type'];

$result[$this->getOwningTable($field)][$columnName] = $newVal;
Expand Down Expand Up @@ -1442,6 +1456,10 @@ protected function getInsertColumnList()
}

if (! $this->class->isIdGeneratorIdentity() || $this->class->identifier[0] !== $name) {
if (! $this->class->isInsertable($name)) {
continue;
}

$columns[] = $this->quoteStrategy->getColumnName($name, $this->class, $this->platform);
$this->columnTypes[$name] = $this->class->fieldMappings[$name]['type'];
}
Expand Down
17 changes: 17 additions & 0 deletions lib/Doctrine/ORM/Persisters/Exception/NonUpdateableField.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace Doctrine\ORM\Persisters\Exception;

use Doctrine\ORM\Exception\PersisterException;

use function sprintf;

final class NonUpdateableField extends PersisterException
{
public static function byName(string $field): self
{
return new self(sprintf('Field is not updateable: %s', $field));
}
}
8 changes: 8 additions & 0 deletions lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,14 @@ public function exportClassMetadata(ClassMetadataInfo $metadata)
if (isset($field['nullable'])) {
$fieldXml->addAttribute('nullable', $field['nullable'] ? 'true' : 'false');
}

if (isset($field['insertable'])) {
$fieldXml->addAttribute('insertable', $field['insertable'] ? 'true' : 'false');
}

if (isset($field['updateable'])) {
$fieldXml->addAttribute('updateable', $field['updateable'] ? 'true' : 'false');
}
}
}

Expand Down
44 changes: 44 additions & 0 deletions tests/Doctrine/Tests/Models/Upsertable/Insertable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Models\Upsertable;

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")
*/
class Insertable
{
/**
* @var int
* @Id
* @GeneratedValue
* @Column(type="integer")
*/
public $id;

/**
* @var string
* @Column(type="string", insertable=false)
*/
public $nonInsertableContent;

/**
* @var string
* @Column(type="string", insertable=true)
*/
public $insertableContent;

/**
* @var string
* @Column(type="string")
*/
public $insertableContentDefault;
}

0 comments on commit 228e907

Please sign in to comment.