Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
11 / 11
CRAP
100.00% covered (success)
100.00%
64 / 64
ReferenceListMigrator
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
11 / 11
29
100.00% covered (success)
100.00%
64 / 64
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
6 / 6
 createAlterStatementList
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
100.00%
11 / 11
 getConstraintByReference
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
4 / 4
 createAlterStatement
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
8 / 8
 compareMapping
100.00% covered (success)
100.00%
1 / 1
6
100.00% covered (success)
100.00%
17 / 17
 areMappingIdentical
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
6 / 6
 getConstraintMappingByReferenceMapping
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
4 / 4
 addDropConstraintStatement
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 addCreateReference
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 getAddForeignKeyStatementList
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getDropForeignKeyStatementList
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
<?php
declare(strict_types = 1);
namespace Siesta\Migration;
use Siesta\Database\MetaData\ConstraintMappingMetaData;
use Siesta\Database\MetaData\ConstraintMetaData;
use Siesta\Database\MigrationStatementFactory;
use Siesta\Model\Reference;
use Siesta\Model\ReferenceMapping;
/**
 * @author Gregor Müller
 */
class ReferenceListMigrator
{
    /**
     * @var ConstraintMetaData[]
     */
    protected $constraintMetaDataList;
    /**
     * @var Reference[]
     */
    protected $referenceList;
    /**
     * @var MigrationStatementFactory
     */
    protected $migrationStatementFactory;
    /**
     * @var string[]
     */
    protected $dropForeignKeyStatementList;
    /**
     * @var string[]
     */
    protected $addForeignKeyStatementList;
    /**
     * ReferenceListMigrator constructor.
     *
     * @param MigrationStatementFactory $migrationStatementFactory
     * @param ConstraintMetaData[] $constraintMetaDataList
     * @param Reference[] $referenceList
     */
    public function __construct(MigrationStatementFactory $migrationStatementFactory, array $constraintMetaDataList, array $referenceList)
    {
        $this->migrationStatementFactory = $migrationStatementFactory;
        $this->constraintMetaDataList = $constraintMetaDataList;
        $this->referenceList = $referenceList;
        $this->addForeignKeyStatementList = [];
        $this->dropForeignKeyStatementList = [];
    }
    /**
     * compares the refernces (foreign key) found in the database with the definition in the model and creates alter
     * statements for columns and foreign key constraints
     * @return void
     */
    public function createAlterStatementList()
    {
        $processedDatabaseList = [];
        // iterate references from model and retrieve alter statements
        foreach ($this->referenceList as $reference) {
            // check if a corresponding database reference exists
            $constraint = $this->getConstraintByReference($reference);
            // retrieve alter statements and add them
            $this->createAlterStatement($constraint, $reference);
            // if a database reference has been found add it to the processed list
            if ($constraint) {
                $processedDatabaseList[] = $constraint->getConstraintName();
            }
        }
        // iterate references from database and retrieve alter statements
        foreach ($this->constraintMetaDataList as $constraintMetaData) {
            // check if reference has already been processed
            if (in_array($constraintMetaData->getConstraintName(), $processedDatabaseList)) {
                continue;
            }
            // no corresponding model reference will result in drop statements
            $this->createAlterStatement($constraintMetaData, null);
        }
    }
    /**
     * @param Reference $reference
     *
     * @return null|ConstraintMetaData
     */
    private function getConstraintByReference(Reference $reference)
    {
        foreach ($this->constraintMetaDataList as $constraintMetaData) {
            if ($constraintMetaData->getConstraintName() === $reference->getConstraintName()) {
                return $constraintMetaData;
            }
        }
        return null;
    }
    /**
     * @param ConstraintMetaData $constraintMetaData
     * @param Reference $reference
     */
    private function createAlterStatement(ConstraintMetaData $constraintMetaData = null, Reference $reference = null)
    {
        if ($constraintMetaData === null) {
            $this->addCreateReference($reference);
            return;
        }
        if ($reference === null) {
            $this->addDropConstraintStatement($constraintMetaData);
            return;
        }
        $this->compareMapping($constraintMetaData, $reference);
    }
    /**
     * @param ConstraintMetaData $constraintMetaData
     * @param Reference $reference
     */
    private function compareMapping(ConstraintMetaData $constraintMetaData, Reference $reference)
    {
        // check if they are referencing the same column
        if ($constraintMetaData->getForeignTable() !== $reference->getForeignTable()) {
            $this->addDropConstraintStatement($constraintMetaData);
            $this->addCreateReference($reference);
            return;
        }
        if (sizeof($constraintMetaData->getConstraintMappingList()) !== sizeof($reference->getReferenceMappingList())) {
            $this->addDropConstraintStatement($constraintMetaData);
            $this->addCreateReference($reference);
            return;
        }
        // modify columns if needed and check if the
        if (!$this->areMappingIdentical($constraintMetaData, $reference)) {
            $this->addDropConstraintStatement($constraintMetaData);
            $this->addCreateReference($reference);
            return;
        }
        // compare on update // >> drop constraint, add constraint
        if ($constraintMetaData->getOnUpdate() === $reference->getOnUpdate() and $constraintMetaData->getOnDelete() === $reference->getOnDelete()) {
            return;
        }
        $this->addCreateReference($reference);
        $this->addDropConstraintStatement($constraintMetaData);
    }
    /**
     * @param ConstraintMetaData $constraintMetaData
     * @param Reference $reference
     *
     * @return bool
     */
    protected function areMappingIdentical(ConstraintMetaData $constraintMetaData, Reference $reference) : bool
    {
        foreach ($reference->getReferenceMappingList() as $referenceMapping) {
            $constraintMappingMetaDataList = $constraintMetaData->getConstraintMappingList();
            $constraintMappingMetaData = $this->getConstraintMappingByReferenceMapping($constraintMappingMetaDataList, $referenceMapping);
            if ($constraintMappingMetaData === null) {
                return false;
            }
        }
        return true;
    }
    /**
     * @param ConstraintMappingMetaData[] $constraintMappingList
     * @param ReferenceMapping $referenceMapping
     *
     * @return ConstraintMappingMetaData|null
     */
    protected function getConstraintMappingByReferenceMapping(array $constraintMappingList, ReferenceMapping $referenceMapping)
    {
        foreach ($constraintMappingList as $constraintMapping) {
            if ($constraintMapping->getLocalColumn() === $referenceMapping->getLocalColumnName() && $constraintMapping->getForeignColumn() === $referenceMapping->getForeignColumnName()) {
                return $constraintMapping;
            }
        }
        return null;
    }
    /**
     * @param ConstraintMetaData $constraintMetaData
     */
    private function addDropConstraintStatement(ConstraintMetaData $constraintMetaData)
    {
        $dropList = $this->migrationStatementFactory->createDropConstraintStatement($constraintMetaData);
        $this->dropForeignKeyStatementList = array_merge($this->dropForeignKeyStatementList, $dropList);
    }
    /**
     * @param Reference $reference
     */
    private function addCreateReference(Reference $reference)
    {
        $addList = $this->migrationStatementFactory->createAddReferenceStatement($reference);
        $this->addForeignKeyStatementList = array_merge($this->addForeignKeyStatementList, $addList);
    }
    /**
     * @return string[]
     */
    public function getAddForeignKeyStatementList()
    {
        return $this->addForeignKeyStatementList;
    }
    /**
     * @return string[]
     */
    public function getDropForeignKeyStatementList()
    {
        return $this->dropForeignKeyStatementList;
    }
}