1<?php 2 3namespace Doctrine\DBAL\Sharding\SQLAzure\Schema; 4 5use Doctrine\DBAL\Schema\Column; 6use Doctrine\DBAL\Schema\ForeignKeyConstraint; 7use Doctrine\DBAL\Schema\Index; 8use Doctrine\DBAL\Schema\Schema; 9use Doctrine\DBAL\Schema\Sequence; 10use Doctrine\DBAL\Schema\Table; 11use Doctrine\DBAL\Schema\Visitor\Visitor; 12use RuntimeException; 13 14use function in_array; 15 16/** 17 * Converts a single tenant schema into a multi-tenant schema for SQL Azure 18 * Federations under the following assumptions: 19 * 20 * - Every table is part of the multi-tenant application, only explicitly 21 * excluded tables are non-federated. The behavior of the tables being in 22 * global or federated database is undefined. It depends on you selecting a 23 * federation before DDL statements or not. 24 * - Every Primary key of a federated table is extended by another column 25 * 'tenant_id' with a default value of the SQLAzure function 26 * `federation_filtering_value('tenant_id')`. 27 * - You always have to work with `filtering=On` when using federations with this 28 * multi-tenant approach. 29 * - Primary keys are either using globally unique ids (GUID, Table Generator) 30 * or you explicitly add the tenant_id in every UPDATE or DELETE statement 31 * (otherwise they will affect the same-id rows from other tenants as well). 32 * SQLAzure throws errors when you try to create IDENTIY columns on federated 33 * tables. 34 * 35 * @deprecated 36 */ 37class MultiTenantVisitor implements Visitor 38{ 39 /** @var string[] */ 40 private $excludedTables = []; 41 42 /** @var string */ 43 private $tenantColumnName; 44 45 /** @var string */ 46 private $tenantColumnType = 'integer'; 47 48 /** 49 * Name of the federation distribution, defaulting to the tenantColumnName 50 * if not specified. 51 * 52 * @var string 53 */ 54 private $distributionName; 55 56 /** 57 * @param string[] $excludedTables 58 * @param string $tenantColumnName 59 * @param string|null $distributionName 60 */ 61 public function __construct(array $excludedTables = [], $tenantColumnName = 'tenant_id', $distributionName = null) 62 { 63 $this->excludedTables = $excludedTables; 64 $this->tenantColumnName = $tenantColumnName; 65 $this->distributionName = $distributionName ?: $tenantColumnName; 66 } 67 68 /** 69 * {@inheritdoc} 70 */ 71 public function acceptTable(Table $table) 72 { 73 if (in_array($table->getName(), $this->excludedTables)) { 74 return; 75 } 76 77 $table->addColumn($this->tenantColumnName, $this->tenantColumnType, [ 78 'default' => "federation_filtering_value('" . $this->distributionName . "')", 79 ]); 80 81 $clusteredIndex = $this->getClusteredIndex($table); 82 83 $indexColumns = $clusteredIndex->getColumns(); 84 $indexColumns[] = $this->tenantColumnName; 85 86 if ($clusteredIndex->isPrimary()) { 87 $table->dropPrimaryKey(); 88 $table->setPrimaryKey($indexColumns); 89 } else { 90 $table->dropIndex($clusteredIndex->getName()); 91 $table->addIndex($indexColumns, $clusteredIndex->getName()); 92 $table->getIndex($clusteredIndex->getName())->addFlag('clustered'); 93 } 94 } 95 96 /** 97 * @param Table $table 98 * 99 * @return Index 100 * 101 * @throws RuntimeException 102 */ 103 private function getClusteredIndex($table) 104 { 105 foreach ($table->getIndexes() as $index) { 106 if ($index->isPrimary() && ! $index->hasFlag('nonclustered')) { 107 return $index; 108 } 109 110 if ($index->hasFlag('clustered')) { 111 return $index; 112 } 113 } 114 115 throw new RuntimeException('No clustered index found on table ' . $table->getName()); 116 } 117 118 /** 119 * {@inheritdoc} 120 */ 121 public function acceptSchema(Schema $schema) 122 { 123 } 124 125 /** 126 * {@inheritdoc} 127 */ 128 public function acceptColumn(Table $table, Column $column) 129 { 130 } 131 132 /** 133 * {@inheritdoc} 134 */ 135 public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) 136 { 137 } 138 139 /** 140 * {@inheritdoc} 141 */ 142 public function acceptIndex(Table $table, Index $index) 143 { 144 } 145 146 /** 147 * {@inheritdoc} 148 */ 149 public function acceptSequence(Sequence $sequence) 150 { 151 } 152} 153