Spaces:
No application file
No application file
namespace Mautic\CoreBundle\Doctrine\Mapping; | |
use Doctrine\DBAL\Types\Types; | |
use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder as OrmClassMetadataBuilder; | |
use Doctrine\ORM\Mapping\Builder\FieldBuilder; | |
use Doctrine\ORM\Mapping\ClassMetadata; | |
use Doctrine\ORM\Mapping\ClassMetadataInfo; | |
use Mautic\CategoryBundle\Entity\Category; | |
use Mautic\CoreBundle\Entity\IpAddress; | |
use Mautic\LeadBundle\Entity\Lead; | |
/** | |
* Override Doctrine's builder classes to add support to orphanRemoval until the fix is incorporated into Doctrine release | |
* See @see https://github.com/doctrine/doctrine2/pull/1326/. | |
*/ | |
class ClassMetadataBuilder extends OrmClassMetadataBuilder | |
{ | |
/** | |
* Max length of indexed VARCHAR fields for UTF8MB4 encoding. | |
*/ | |
public const MAX_VARCHAR_INDEXED_LENGTH = 191; | |
public function __construct(ClassMetadataInfo $cm) | |
{ | |
parent::__construct($cm); | |
// Default all Mautic entities to explicit | |
$this->setChangeTrackingPolicyDeferredExplicit(); | |
} | |
/** | |
* Creates a ManyToOne Association Builder. | |
* | |
* Note: This method does not add the association, you have to call build() on the AssociationBuilder. | |
* | |
* @param string $name | |
* @param string $targetEntity | |
* | |
* @return AssociationBuilder | |
*/ | |
public function createManyToOne($name, $targetEntity) | |
{ | |
return new AssociationBuilder( | |
$this, | |
[ | |
'fieldName' => $name, | |
'targetEntity' => $targetEntity, | |
], | |
ClassMetadata::MANY_TO_ONE | |
); | |
} | |
/** | |
* Creates a OneToOne Association Builder. | |
* | |
* @param string $name | |
* @param string $targetEntity | |
* | |
* @return AssociationBuilder | |
*/ | |
public function createOneToOne($name, $targetEntity) | |
{ | |
return new AssociationBuilder( | |
$this, | |
[ | |
'fieldName' => $name, | |
'targetEntity' => $targetEntity, | |
], | |
ClassMetadata::ONE_TO_ONE | |
); | |
} | |
/** | |
* Creates a ManyToMany Association Builder. | |
* | |
* @param string $name | |
* @param string $targetEntity | |
* | |
* @return ManyToManyAssociationBuilder | |
*/ | |
public function createManyToMany($name, $targetEntity) | |
{ | |
return new ManyToManyAssociationBuilder( | |
$this, | |
[ | |
'fieldName' => $name, | |
'targetEntity' => $targetEntity, | |
], | |
ClassMetadata::MANY_TO_MANY | |
); | |
} | |
/** | |
* Creates a one to many association builder. | |
* | |
* @param string $name | |
* @param string $targetEntity | |
* | |
* @return OneToManyAssociationBuilder | |
*/ | |
public function createOneToMany($name, $targetEntity) | |
{ | |
return new OneToManyAssociationBuilder( | |
$this, | |
[ | |
'fieldName' => $name, | |
'targetEntity' => $targetEntity, | |
], | |
ClassMetadata::ONE_TO_MANY | |
); | |
} | |
/** | |
* Add Id column. | |
* | |
* @return $this | |
*/ | |
public function addId() | |
{ | |
$this->createField('id', Types::INTEGER) | |
->makePrimaryKey() | |
->generatedValue() | |
->option('unsigned', true) | |
->build(); | |
return $this; | |
} | |
/** | |
* Adds autogenerated ID field type of BIGINT UNSIGNED. | |
* | |
* @param string $columnName | |
* @param bool $isPrimary | |
* @param bool $isNullable | |
* | |
* @return ClassMetadataBuilder | |
*/ | |
public function addBigIntIdField($fieldName = 'id', $columnName = 'id', $isPrimary = true, $isNullable = false) | |
{ | |
$cm = $this->getClassMetadata(); | |
$cm->mapField( | |
[ | |
'fieldName' => $fieldName, | |
'columnName' => $columnName, | |
'id' => $isPrimary, | |
'nullable' => $isNullable, | |
'type' => Types::BIGINT, | |
'options' => [ | |
'unsigned' => true, | |
], | |
] | |
); | |
if ($isPrimary) { | |
$cm->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_AUTO); | |
} | |
return $this; | |
} | |
/** | |
* Add UUID as Id. | |
* | |
* @return $this | |
*/ | |
public function addUuid() | |
{ | |
$this->createField('id', 'guid') | |
->makePrimaryKey() | |
->build(); | |
return $this; | |
} | |
/** | |
* Add id, name, and description columns. | |
* | |
* @param string $nameColumn | |
* @param string $descriptionColumn | |
* | |
* @return $this | |
*/ | |
public function addIdColumns($nameColumn = 'name', $descriptionColumn = 'description') | |
{ | |
$this->addId(); | |
if ($nameColumn) { | |
$this->createField($nameColumn, Types::STRING) | |
->build(); | |
} | |
if ($descriptionColumn) { | |
$this->createField($descriptionColumn, Types::TEXT) | |
->nullable() | |
->build(); | |
} | |
return $this; | |
} | |
/** | |
* Add category to metadata. | |
* | |
* @return $this | |
*/ | |
public function addCategory() | |
{ | |
$this->createManyToOne('category', Category::class) | |
->cascadeMerge() | |
->cascadeDetach() | |
->addJoinColumn('category_id', 'id', true, false, 'SET NULL') | |
->build(); | |
return $this; | |
} | |
/** | |
* Add publish up and down dates to metadata. | |
* | |
* @return $this | |
*/ | |
public function addPublishDates() | |
{ | |
$this->createField('publishUp', Types::DATETIME_MUTABLE) | |
->columnName('publish_up') | |
->nullable() | |
->build(); | |
$this->createField('publishDown', Types::DATETIME_MUTABLE) | |
->columnName('publish_down') | |
->nullable() | |
->build(); | |
return $this; | |
} | |
/** | |
* Added dateAdded column. | |
* | |
* @param bool|false $nullable | |
* | |
* @return $this | |
*/ | |
public function addDateAdded($nullable = false) | |
{ | |
$dateAdded = $this->createField('dateAdded', Types::DATETIME_MUTABLE) | |
->columnName('date_added'); | |
if ($nullable) { | |
$dateAdded->nullable(); | |
} | |
$dateAdded->build(); | |
return $this; | |
} | |
/** | |
* Add a contact column. | |
* | |
* @param bool|false $nullable | |
* @param string $onDelete | |
* @param bool|false $isPrimaryKey | |
* @param string|null $inversedBy | |
* | |
* @return $this | |
*/ | |
public function addContact($nullable = false, $onDelete = 'CASCADE', $isPrimaryKey = false, $inversedBy = null) | |
{ | |
$lead = $this->createManyToOne('contact', Lead::class); | |
if ($isPrimaryKey) { | |
$lead->makePrimaryKey(); | |
} | |
if ($inversedBy) { | |
$lead->inversedBy($inversedBy); | |
} | |
$lead | |
->addJoinColumn('contact_id', 'id', $nullable, false, $onDelete) | |
->build(); | |
return $this; | |
} | |
/** | |
* Add a lead column. | |
* | |
* @param bool|false $nullable | |
* @param string $onDelete | |
* @param bool|false $isPrimaryKey | |
* | |
* @deprecated Use addContact instead; existing implementations will need a migration to rename lead_id to contact_id | |
* | |
* @return $this | |
*/ | |
public function addLead($nullable = false, $onDelete = 'CASCADE', $isPrimaryKey = false, $inversedBy = null) | |
{ | |
$lead = $this->createManyToOne('lead', Lead::class); | |
if ($isPrimaryKey) { | |
$lead->makePrimaryKey(); | |
} | |
if ($inversedBy) { | |
$lead->inversedBy($inversedBy); | |
} | |
$lead | |
->addJoinColumn('lead_id', 'id', $nullable, false, $onDelete) | |
->build(); | |
return $this; | |
} | |
/** | |
* Adds IP address. | |
* | |
* @param bool $nullable | |
* | |
* @return $this | |
*/ | |
public function addIpAddress($nullable = false) | |
{ | |
$this->createManyToOne('ipAddress', IpAddress::class) | |
->cascadePersist() | |
->cascadeMerge() | |
->cascadeDetach() | |
->addJoinColumn('ip_id', 'id', $nullable, false, 'SET NULL') | |
->build(); | |
return $this; | |
} | |
/** | |
* Add a nullable field. | |
* | |
* @param string $name | |
* @param string $type | |
* @param string|null $columnName | |
* | |
* @return $this | |
*/ | |
public function addNullableField($name, $type = Types::STRING, $columnName = null) | |
{ | |
$field = $this->createField($name, $type) | |
->nullable(); | |
if (null !== $columnName) { | |
$field->columnName($columnName); | |
} | |
if ($this->isIndexedVarchar($columnName ?? $name, $type)) { | |
$field->length(self::MAX_VARCHAR_INDEXED_LENGTH); | |
} | |
$field->build(); | |
return $this; | |
} | |
/** | |
* Add a field with a custom column name. | |
* | |
* @param bool $nullable | |
* | |
* @return $this | |
*/ | |
public function addNamedField($name, $type, $columnName, $nullable = false) | |
{ | |
$field = $this->createField($name, $type) | |
->columnName($columnName); | |
if ($nullable) { | |
$field->nullable(); | |
} | |
if ($this->isIndexedVarchar($columnName ?? $name, $type)) { | |
$field->length(self::MAX_VARCHAR_INDEXED_LENGTH); | |
} | |
$field->build(); | |
return $this; | |
} | |
/** | |
* Adds Field. Overridden for IDE suggestions when stringing methods in entity class. | |
* | |
* @param string $name | |
* @param string $type | |
* | |
* @return $this | |
*/ | |
public function addField($name, $type, array $mapping = []) | |
{ | |
if ($this->isIndexedVarchar($name, $type)) { | |
$mapping['length'] = self::MAX_VARCHAR_INDEXED_LENGTH; | |
} | |
return parent::addField($name, $type, $mapping); | |
} | |
public function createField($name, $type) | |
{ | |
$mapping = [ | |
'fieldName' => $name, | |
'type' => $type, | |
]; | |
if ($this->isIndexedVarchar($name, $type)) { | |
$mapping['length'] = self::MAX_VARCHAR_INDEXED_LENGTH; | |
} | |
return new FieldBuilder($this, $mapping); | |
} | |
/** | |
* @param string $name | |
* @param mixed[] $flags | |
* @param mixed[] $options | |
*/ | |
public function addIndex(array $columns, $name, array $flags = null, array $options = null): self | |
{ | |
$cm = $this->getClassMetadata(); | |
if (!isset($cm->table['indexes'])) { | |
$cm->table['indexes'] = []; | |
} | |
$definition = ['columns' => $columns]; | |
if (null !== $flags) { | |
$definition['flags'] = $flags; | |
} | |
if (null !== $options) { | |
$definition['options'] = $options; | |
} | |
$cm->table['indexes'][$name] = $definition; | |
return $this; | |
} | |
/** | |
* @deprecated this method will be removed as MySQL does not support partial indices whatsoever | |
* | |
* @param string $name | |
* @param string $where | |
*/ | |
public function addPartialIndex(array $columns, $name, $where): ClassMetadataBuilder | |
{ | |
return $this->addIndex($columns, $name, null, ['where' => $where]); | |
} | |
/** | |
* @param mixed[] $columns | |
*/ | |
public function addFulltextIndex(array $columns, string $name): self | |
{ | |
return $this->addIndex($columns, $name, ['fulltext']); | |
} | |
/** | |
* UTF8MB4 encoding needs max length of 191 instead of 255 that UTF8 needed. Doctrine does not take care of it by itself. | |
*/ | |
public function isIndexedVarchar(string $name, string $type): bool | |
{ | |
return Types::STRING === $type || isset($this->getClassMetadata()->table['indexes'][$name]); | |
} | |
/** | |
* Adds Index with options. | |
* | |
* @param list<string> $columns | |
* @param array<string, mixed> $options | |
*/ | |
public function addIndexWithOptions(array $columns, string $name, array $options): ClassMetadataBuilder | |
{ | |
$cm = $this->getClassMetadata(); | |
$cm->table['indexes'][$name] = [ | |
'columns' => $columns, | |
'options' => $options, | |
]; | |
return $this; | |
} | |
} | |