Skip to content

Commit

Permalink
[GH-8345] Fields for unique constraints (#8629)
Browse files Browse the repository at this point in the history
* Add possibility to use fields instead of column for unique constraint and indexes (#8345)

* Document changes in annotation reference

* phpcs

* Ensure exactly one of fields/columns is set for index/uniqueConstraint

* Adapt docs to fields/columns changes

* phpcs

* Implement fields in Attribute driver and fix mapping classes constructors.

* Coding Standard

* Apply suggestions from code review

Co-authored-by: Grégoire Paris <[email protected]>

* phpcs

Co-authored-by: Jakub Caban <[email protected]>
Co-authored-by: Grégoire Paris <[email protected]>
  • Loading branch information
3 people authored Apr 18, 2021
1 parent 261a405 commit a68aa58
Show file tree
Hide file tree
Showing 21 changed files with 734 additions and 25 deletions.
32 changes: 30 additions & 2 deletions docs/en/reference/annotations-reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,8 @@ Required attributes:


- **name**: Name of the Index
- **columns**: Array of columns.
- **fields**: Array of fields. Exactly one of **fields**, **columns** is required.
- **columns**: Array of columns. Exactly one of **fields**, **columns** is required.

Optional attributes:

Expand All @@ -535,6 +536,19 @@ Basic example:
{
}
Basic example using fields:

.. code-block:: php
<?php
/**
* @Entity
* @Table(name="ecommerce_products",indexes={@Index(name="search_idx", fields={"name", "email"})})
*/
class ECommerceProduct
{
}
Example with partial indexes:

.. code-block:: php
Expand Down Expand Up @@ -1272,7 +1286,8 @@ Required attributes:


- **name**: Name of the Index
- **columns**: Array of columns.
- **fields**: Array of fields. Exactly one of **fields**, **columns** is required.
- **columns**: Array of columns. Exactly one of **fields**, **columns** is required.

Optional attributes:

Expand All @@ -1294,6 +1309,19 @@ Basic example:
{
}
Basic example using fields:

.. code-block:: php
<?php
/**
* @Entity
* @Table(name="ecommerce_products",uniqueConstraints={@UniqueConstraint(name="search_idx", fields={"name", "email"})})
*/
class ECommerceProduct
{
}
Example with partial indexes:

.. code-block:: php
Expand Down
4 changes: 3 additions & 1 deletion doctrine-mapping.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,8 @@
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="optional"/>
<xs:attribute name="columns" type="xs:string" use="required"/>
<xs:attribute name="columns" type="xs:string" use="optional"/>
<xs:attribute name="fields" type="xs:string" use="optional"/>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>

Expand All @@ -351,6 +352,7 @@
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="optional"/>
<xs:attribute name="columns" type="xs:string" use="required"/>
<xs:attribute name="fields" type="xs:string" use="optional"/>
<xs:attribute name="flags" type="xs:string" use="optional"/>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
Expand Down
47 changes: 45 additions & 2 deletions lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

use function class_exists;
use function constant;
use function count;
use function defined;
use function get_class;
use function is_array;
Expand Down Expand Up @@ -110,7 +111,28 @@ public function loadMetadataForClass($className, ClassMetadata $metadata)

if ($tableAnnot->indexes !== null) {
foreach ($tableAnnot->indexes as $indexAnnot) {
$index = ['columns' => $indexAnnot->columns];
$index = [];

if (! empty($indexAnnot->columns)) {
$index['columns'] = $indexAnnot->columns;
}

if (! empty($indexAnnot->fields)) {
$index['fields'] = $indexAnnot->fields;
}

if (
isset($index['columns'], $index['fields'])
|| (
! isset($index['columns'])
&& ! isset($index['fields'])
)
) {
throw MappingException::invalidIndexConfiguration(
$className,
(string) ($indexAnnot->name ?? count($primaryTable['indexes']))
);
}

if (! empty($indexAnnot->flags)) {
$index['flags'] = $indexAnnot->flags;
Expand All @@ -130,7 +152,28 @@ public function loadMetadataForClass($className, ClassMetadata $metadata)

if ($tableAnnot->uniqueConstraints !== null) {
foreach ($tableAnnot->uniqueConstraints as $uniqueConstraintAnnot) {
$uniqueConstraint = ['columns' => $uniqueConstraintAnnot->columns];
$uniqueConstraint = [];

if (! empty($uniqueConstraintAnnot->columns)) {
$uniqueConstraint['columns'] = $uniqueConstraintAnnot->columns;
}

if (! empty($uniqueConstraintAnnot->fields)) {
$uniqueConstraint['fields'] = $uniqueConstraintAnnot->fields;
}

if (
isset($uniqueConstraint['columns'], $uniqueConstraint['fields'])
|| (
! isset($uniqueConstraint['columns'])
&& ! isset($uniqueConstraint['fields'])
)
) {
throw MappingException::invalidUniqueConstraintConfiguration(
$className,
(string) ($uniqueConstraintAnnot->name ?? count($primaryTable['uniqueConstraints']))
);
}

if (! empty($uniqueConstraintAnnot->options)) {
$uniqueConstraint['options'] = $uniqueConstraintAnnot->options;
Expand Down
47 changes: 45 additions & 2 deletions lib/Doctrine/ORM/Mapping/Driver/AttributeDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use function assert;
use function class_exists;
use function constant;
use function count;
use function defined;

class AttributeDriver extends AnnotationDriver
Expand Down Expand Up @@ -76,7 +77,28 @@ public function loadMetadataForClass($className, ClassMetadata $metadata): void

if (isset($classAttributes[Mapping\Index::class])) {
foreach ($classAttributes[Mapping\Index::class] as $indexAnnot) {
$index = ['columns' => $indexAnnot->columns];
$index = [];

if (! empty($indexAnnot->columns)) {
$index['columns'] = $indexAnnot->columns;
}

if (! empty($indexAnnot->fields)) {
$index['fields'] = $indexAnnot->fields;
}

if (
isset($index['columns'], $index['fields'])
|| (
! isset($index['columns'])
&& ! isset($index['fields'])
)
) {
throw MappingException::invalidIndexConfiguration(
$className,
(string) ($indexAnnot->name ?? count($primaryTable['indexes']))
);
}

if (! empty($indexAnnot->flags)) {
$index['flags'] = $indexAnnot->flags;
Expand All @@ -96,7 +118,28 @@ public function loadMetadataForClass($className, ClassMetadata $metadata): void

if (isset($classAttributes[Mapping\UniqueConstraint::class])) {
foreach ($classAttributes[Mapping\UniqueConstraint::class] as $uniqueConstraintAnnot) {
$uniqueConstraint = ['columns' => $uniqueConstraintAnnot->columns];
$uniqueConstraint = [];

if (! empty($uniqueConstraintAnnot->columns)) {
$uniqueConstraint['columns'] = $uniqueConstraintAnnot->columns;
}

if (! empty($uniqueConstraintAnnot->fields)) {
$uniqueConstraint['fields'] = $uniqueConstraintAnnot->fields;
}

if (
isset($uniqueConstraint['columns'], $uniqueConstraint['fields'])
|| (
! isset($uniqueConstraint['columns'])
&& ! isset($uniqueConstraint['fields'])
)
) {
throw MappingException::invalidUniqueConstraintConfiguration(
$className,
(string) ($uniqueConstraintAnnot->name ?? count($primaryTable['uniqueConstraints']))
);
}

if (! empty($uniqueConstraintAnnot->options)) {
$uniqueConstraint['options'] = $uniqueConstraintAnnot->options;
Expand Down
47 changes: 45 additions & 2 deletions lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

use function assert;
use function constant;
use function count;
use function defined;
use function explode;
use function file_get_contents;
Expand Down Expand Up @@ -212,7 +213,28 @@ public function loadMetadataForClass($className, ClassMetadata $metadata)
if (isset($xmlRoot->indexes)) {
$metadata->table['indexes'] = [];
foreach ($xmlRoot->indexes->index as $indexXml) {
$index = ['columns' => explode(',', (string) $indexXml['columns'])];
$index = [];

if (isset($indexXml['columns']) && ! empty($indexXml['columns'])) {
$index['columns'] = explode(',', (string) $indexXml['columns']);
}

if (isset($indexXml['fields'])) {
$index['fields'] = explode(',', (string) $indexXml['fields']);
}

if (
isset($index['columns'], $index['fields'])
|| (
! isset($index['columns'])
&& ! isset($index['fields'])
)
) {
throw MappingException::invalidIndexConfiguration(
$className,
(string) ($indexXml['name'] ?? count($metadata->table['indexes']))
);
}

if (isset($indexXml['flags'])) {
$index['flags'] = explode(',', (string) $indexXml['flags']);
Expand All @@ -234,7 +256,28 @@ public function loadMetadataForClass($className, ClassMetadata $metadata)
if (isset($xmlRoot->{'unique-constraints'})) {
$metadata->table['uniqueConstraints'] = [];
foreach ($xmlRoot->{'unique-constraints'}->{'unique-constraint'} as $uniqueXml) {
$unique = ['columns' => explode(',', (string) $uniqueXml['columns'])];
$unique = [];

if (isset($uniqueXml['columns']) && ! empty($uniqueXml['columns'])) {
$unique['columns'] = explode(',', (string) $uniqueXml['columns']);
}

if (isset($uniqueXml['fields'])) {
$unique['fields'] = explode(',', (string) $uniqueXml['fields']);
}

if (
isset($unique['columns'], $unique['fields'])
|| (
! isset($unique['columns'])
&& ! isset($unique['fields'])
)
) {
throw MappingException::invalidUniqueConstraintConfiguration(
$className,
(string) ($uniqueXml['name'] ?? count($metadata->table['uniqueConstraints']))
);
}

if (isset($uniqueXml->options)) {
$unique['options'] = $this->parseOptions($uniqueXml->options->children());
Expand Down
66 changes: 58 additions & 8 deletions lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -228,10 +228,35 @@ public function loadMetadataForClass($className, ClassMetadata $metadata)
$indexYml['name'] = $name;
}

if (is_string($indexYml['columns'])) {
$index = ['columns' => array_map('trim', explode(',', $indexYml['columns']))];
} else {
$index = ['columns' => $indexYml['columns']];
$index = [];

if (isset($indexYml['columns'])) {
if (is_string($indexYml['columns'])) {
$index['columns'] = array_map('trim', explode(',', $indexYml['columns']));
} else {
$index['columns'] = $indexYml['columns'];
}
}

if (isset($indexYml['fields'])) {
if (is_string($indexYml['fields'])) {
$index['fields'] = array_map('trim', explode(',', $indexYml['fields']));
} else {
$index['fields'] = $indexYml['fields'];
}
}

if (
isset($index['columns'], $index['fields'])
|| (
! isset($index['columns'])
&& ! isset($index['fields'])
)
) {
throw MappingException::invalidIndexConfiguration(
$className,
$indexYml['name']
);
}

if (isset($indexYml['flags'])) {
Expand All @@ -257,10 +282,35 @@ public function loadMetadataForClass($className, ClassMetadata $metadata)
$uniqueYml['name'] = $name;
}

if (is_string($uniqueYml['columns'])) {
$unique = ['columns' => array_map('trim', explode(',', $uniqueYml['columns']))];
} else {
$unique = ['columns' => $uniqueYml['columns']];
$unique = [];

if (isset($uniqueYml['columns'])) {
if (is_string($uniqueYml['columns'])) {
$unique['columns'] = array_map('trim', explode(',', $uniqueYml['columns']));
} else {
$unique['columns'] = $uniqueYml['columns'];
}
}

if (isset($uniqueYml['fields'])) {
if (is_string($uniqueYml['fields'])) {
$unique['fields'] = array_map('trim', explode(',', $uniqueYml['fields']));
} else {
$unique['fields'] = $uniqueYml['fields'];
}
}

if (
isset($unique['columns'], $unique['fields'])
|| (
! isset($unique['columns'])
&& ! isset($unique['fields'])
)
) {
throw MappingException::invalidUniqueConstraintConfiguration(
$className,
$uniqueYml['name']
);
}

if (isset($uniqueYml['options'])) {
Expand Down
8 changes: 7 additions & 1 deletion lib/Doctrine/ORM/Mapping/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ final class Index implements Annotation
/** @var array<string> */
public $columns;

/** @var array<string> */
public $fields;

/** @var array<string> */
public $flags;

Expand All @@ -45,16 +48,19 @@ final class Index implements Annotation

/**
* @param array<string> $columns
* @param array<string> $fields
* @param array<string> $flags
* @param array<string> $options
*/
public function __construct(
array $columns,
?array $columns = null,
?array $fields = null,
?string $name = null,
?array $flags = null,
?array $options = null
) {
$this->columns = $columns;
$this->fields = $fields;
$this->name = $name;
$this->flags = $flags;
$this->options = $options;
Expand Down
Loading

0 comments on commit a68aa58

Please sign in to comment.