Skip to content

Commit

Permalink
Merge pull request #7799 from doctrine/m2m-association-builder
Browse files Browse the repository at this point in the history
Many to many association builder
  • Loading branch information
guilhermeblanco committed Aug 19, 2019
2 parents 4bfa223 + 5afceb9 commit 1488955
Show file tree
Hide file tree
Showing 19 changed files with 1,192 additions and 960 deletions.
159 changes: 159 additions & 0 deletions lib/Doctrine/ORM/Mapping/Builder/AssociationMetadataBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<?php

declare(strict_types=1);

namespace Doctrine\ORM\Mapping\Builder;

use Doctrine\ORM\Annotation;
use Doctrine\ORM\Mapping;
use function array_diff;
use function array_intersect;
use function array_map;
use function class_exists;
use function constant;
use function count;
use function defined;
use function in_array;
use function interface_exists;
use function sprintf;

abstract class AssociationMetadataBuilder
{
/** @var Mapping\ClassMetadataBuildingContext */
protected $metadataBuildingContext;

/** @var CacheMetadataBuilder */
protected $cacheMetadataBuilder;

/** @var Mapping\ClassMetadata */
protected $componentMetadata;

/** @var string */
protected $fieldName;

/** @var Annotation\Cache|null */
protected $cacheAnnotation;

public function __construct(
Mapping\ClassMetadataBuildingContext $metadataBuildingContext,
?CacheMetadataBuilder $cacheMetadataBuilder = null
) {
$this->metadataBuildingContext = $metadataBuildingContext;
$this->cacheMetadataBuilder = $cacheMetadataBuilder ?: new CacheMetadataBuilder($metadataBuildingContext);
}

public function withComponentMetadata(Mapping\ClassMetadata $componentMetadata) : AssociationMetadataBuilder
{
$this->componentMetadata = $componentMetadata;

$this->cacheMetadataBuilder->withComponentMetadata($componentMetadata);

return $this;
}

public function withFieldName(string $fieldName) : AssociationMetadataBuilder
{
$this->fieldName = $fieldName;

$this->cacheMetadataBuilder->withFieldName($fieldName);

return $this;
}

public function withCacheAnnotation(?Annotation\Cache $cacheAnnotation) : AssociationMetadataBuilder
{
$this->cacheAnnotation = $cacheAnnotation;

if ($cacheAnnotation !== null) {
$this->cacheMetadataBuilder->withCacheAnnotation($cacheAnnotation);
}

return $this;
}

protected function buildCache(Mapping\AssociationMetadata $associationMetadata) : void
{
if ($this->cacheAnnotation !== null) {
$associationMetadata->setCache($this->cacheMetadataBuilder->build());
}
}

/**
* Attempts to resolve target entity.
*
* @param string $targetEntity The proposed target entity
*
* @return string The processed target entity
*
* @throws Mapping\MappingException If a target entity is not valid.
*/
protected function getTargetEntity(string $targetEntity) : string
{
// Validate if target entity is defined
if (! $targetEntity) {
throw Mapping\MappingException::missingTargetEntity($this->fieldName);
}

// Validate that target entity exists
if (! (class_exists($targetEntity) || interface_exists($targetEntity))) {
throw Mapping\MappingException::invalidTargetEntityClass(
$targetEntity,
$this->componentMetadata->getClassName(),
$this->fieldName
);
}

return $targetEntity;
}

/**
* Attempts to resolve the cascade modes.
*
* @param string[] $originalCascades The original unprocessed field cascades.
*
* @return string[] The processed field cascades.
*
* @throws Mapping\MappingException If a cascade option is not valid.
*/
protected function getCascade(array $originalCascades) : array
{
$cascadeTypes = ['remove', 'persist', 'refresh'];
$cascades = array_map('strtolower', $originalCascades);

if (in_array('all', $cascades, true)) {
$cascades = $cascadeTypes;
}

if (count($cascades) !== count(array_intersect($cascades, $cascadeTypes))) {
$diffCascades = array_diff($cascades, array_intersect($cascades, $cascadeTypes));

throw Mapping\MappingException::invalidCascadeOption(
$diffCascades,
$this->componentMetadata->getClassName(),
$this->fieldName
);
}

return $cascades;
}

/**
* Attempts to resolve the fetch mode.
*
* @param string $fetchMode The fetch mode.
*
* @return string The fetch mode as defined in ClassMetadata.
*
* @throws Mapping\MappingException If the fetch mode is not valid.
*/
protected function getFetchMode($fetchMode) : string
{
$fetchModeConstant = sprintf('%s::%s', Mapping\FetchMode::class, $fetchMode);

if (! defined($fetchModeConstant)) {
throw Mapping\MappingException::invalidFetchMode($this->componentMetadata->getClassName(), $fetchMode);
}

return constant($fetchModeConstant);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php

declare(strict_types=1);

namespace Doctrine\ORM\Mapping\Builder;

use Doctrine\ORM\Annotation;
use Doctrine\ORM\Mapping;
use function array_merge;
use function array_unique;
use function assert;

class ManyToManyAssociationMetadataBuilder extends ToManyAssociationMetadataBuilder
{
/** @var JoinTableMetadataBuilder */
private $joinTableMetadataBuilder;

/** @var Annotation\ManyToMany */
private $manyToManyAnnotation;

/** @var Annotation\JoinTable|null */
private $joinTableAnnotation;

public function __construct(
Mapping\ClassMetadataBuildingContext $metadataBuildingContext,
?JoinTableMetadataBuilder $joinTableMetadataBuilder = null,
?CacheMetadataBuilder $cacheMetadataBuilder = null
) {
parent::__construct($metadataBuildingContext, $cacheMetadataBuilder);

$this->joinTableMetadataBuilder = $joinTableMetadataBuilder ?: new JoinTableMetadataBuilder($metadataBuildingContext);
}

public function withComponentMetadata(Mapping\ClassMetadata $componentMetadata) : AssociationMetadataBuilder
{
parent::withComponentMetadata($componentMetadata);

$this->joinTableMetadataBuilder->withComponentMetadata($componentMetadata);

return $this;
}

public function withFieldName(string $fieldName) : AssociationMetadataBuilder
{
parent::withFieldName($fieldName);

$this->joinTableMetadataBuilder->withFieldName($fieldName);

return $this;
}

public function withManyToManyAnnotation(Annotation\ManyToMany $manyToManyAnnotation) : ManyToManyAssociationMetadataBuilder
{
$this->manyToManyAnnotation = $manyToManyAnnotation;

return $this;
}

public function withJoinTableAnnotation(?Annotation\JoinTable $joinTableAnnotation) : ManyToManyAssociationMetadataBuilder
{
$this->joinTableAnnotation = $joinTableAnnotation;

if ($joinTableAnnotation !== null) {
$this->joinTableMetadataBuilder->withJoinTableAnnotation($joinTableAnnotation);
}

return $this;
}

/**
* @internal Association metadata order of definition settings is important.
*/
public function build() : Mapping\ManyToManyAssociationMetadata
{
// Validate required fields
assert($this->componentMetadata !== null);
assert($this->manyToManyAnnotation !== null);
assert($this->fieldName !== null);

$componentClassName = $this->componentMetadata->getClassName();
$associationMetadata = new Mapping\ManyToManyAssociationMetadata($this->fieldName);

$associationMetadata->setSourceEntity($componentClassName);
$associationMetadata->setTargetEntity($this->getTargetEntity($this->manyToManyAnnotation->targetEntity));
$associationMetadata->setCascade($this->getCascade($this->manyToManyAnnotation->cascade));
$associationMetadata->setFetchMode($this->getFetchMode($this->manyToManyAnnotation->fetch));
$associationMetadata->setOwningSide(true);

if (! empty($this->manyToManyAnnotation->mappedBy)) {
$associationMetadata->setMappedBy($this->manyToManyAnnotation->mappedBy);
$associationMetadata->setOwningSide(false);
}

if (! empty($this->manyToManyAnnotation->inversedBy)) {
$associationMetadata->setInversedBy($this->manyToManyAnnotation->inversedBy);
}

if (! empty($this->manyToManyAnnotation->indexBy)) {
$associationMetadata->setIndexedBy($this->manyToManyAnnotation->indexBy);
}

if ($this->manyToManyAnnotation->orphanRemoval) {
$associationMetadata->setOrphanRemoval($this->manyToManyAnnotation->orphanRemoval);

// Orphan removal also implies a cascade remove
$associationMetadata->setCascade(array_unique(array_merge($associationMetadata->getCascade(), ['remove'])));
}

if ($this->orderByAnnotation !== null) {
$associationMetadata->setOrderBy($this->orderByAnnotation->value);
}

$this->buildCache($associationMetadata);

// Check for owning side to consider join column
if (! $associationMetadata->isOwningSide()) {
return $associationMetadata;
}

$this->buildJoinTable($associationMetadata);

return $associationMetadata;
}

protected function buildJoinTable(Mapping\ManyToManyAssociationMetadata $associationMetadata) : void
{
$this->joinTableMetadataBuilder->withTargetEntity($associationMetadata->getTargetEntity());

$associationMetadata->setJoinTable($this->joinTableMetadataBuilder->build());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

namespace Doctrine\ORM\Mapping\Builder;

use Doctrine\ORM\Annotation;
use Doctrine\ORM\Mapping;
use function assert;

class ManyToOneAssociationMetadataBuilder extends ToOneAssociationMetadataBuilder
{
/** @var Annotation\ManyToOne */
private $manyToOneAnnotation;

public function withManyToOneAnnotation(Annotation\ManyToOne $manyToOneAnnotation) : ManyToOneAssociationMetadataBuilder
{
$this->manyToOneAnnotation = $manyToOneAnnotation;

return $this;
}

/**
* @internal Association metadata order of definition settings is important.
*/
public function build() : Mapping\ManyToOneAssociationMetadata
{
// Validate required fields
assert($this->componentMetadata !== null);
assert($this->manyToOneAnnotation !== null);
assert($this->fieldName !== null);

$componentClassName = $this->componentMetadata->getClassName();
$associationMetadata = new Mapping\ManyToOneAssociationMetadata($this->fieldName);

$associationMetadata->setSourceEntity($componentClassName);
$associationMetadata->setTargetEntity($this->getTargetEntity($this->manyToOneAnnotation->targetEntity));
$associationMetadata->setCascade($this->getCascade($this->manyToOneAnnotation->cascade));
$associationMetadata->setFetchMode($this->getFetchMode($this->manyToOneAnnotation->fetch));
$associationMetadata->setOwningSide(true);

if (! empty($this->manyToOneAnnotation->inversedBy)) {
$associationMetadata->setInversedBy($this->manyToOneAnnotation->inversedBy);
}

$this->buildCache($associationMetadata);
$this->buildPrimaryKey($associationMetadata);
$this->buildJoinColumns($associationMetadata);

return $associationMetadata;
}
}

0 comments on commit 1488955

Please sign in to comment.