|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace Ubiquity\db\reverse; |
|
4
|
|
|
|
|
5
|
|
|
use Ubiquity\orm\reverse\TableReversor; |
|
6
|
|
|
use Ubiquity\orm\OrmUtils; |
|
7
|
|
|
use Ubiquity\cache\ClassUtils; |
|
8
|
|
|
|
|
9
|
|
|
class DbGenerator { |
|
10
|
|
|
protected $nameProtection; |
|
11
|
|
|
protected $createDatabaseMask; |
|
12
|
|
|
protected $createTableMask; |
|
13
|
|
|
protected $fieldMask; |
|
14
|
|
|
protected $foreignKeyMask; |
|
15
|
|
|
protected $alterTableMask; |
|
16
|
|
|
protected $alterTableAddKey; |
|
17
|
|
|
protected $autoIncMask; |
|
18
|
|
|
protected $selectDbMask; |
|
19
|
|
|
protected $constraintNames=[]; |
|
20
|
|
|
protected $sqlScript=[]; |
|
21
|
|
|
protected $fieldTypes; |
|
22
|
|
|
protected $defaultType; |
|
23
|
|
|
protected $typeMatch='@([\s\S]*?)((?:\((?:\d)+\))*?)$@'; |
|
24
|
|
|
protected $intMatch='@^.*?int.*?((?:\((?:\d)+\))*?)$@'; |
|
25
|
|
|
protected $manyToManys=[]; |
|
26
|
|
|
|
|
27
|
|
|
|
|
28
|
|
|
public function isInt($fieldType){ |
|
29
|
|
|
return \preg_match($this->intMatch, $fieldType); |
|
30
|
|
|
} |
|
31
|
|
|
public function __construct(){ |
|
32
|
|
|
$this->nameProtection="`"; |
|
33
|
|
|
$this->createDatabaseMask="CREATE DATABASE %name%"; |
|
34
|
|
|
$this->selectDbMask="USE %name%"; |
|
35
|
|
|
$this->createTableMask="CREATE TABLE %name% (%fields%) %attributes%"; |
|
36
|
|
|
$this->fieldMask="%name% %type% %extra%"; |
|
37
|
|
|
$this->alterTableMask="ALTER TABLE %tableName% %alter%"; |
|
38
|
|
|
$this->foreignKeyMask="ALTER TABLE %tableName% ADD CONSTRAINT %fkName% FOREIGN KEY (%fkFieldName%) REFERENCES %referencesTableName% (%referencesFieldName%) ON DELETE %onDelete% ON UPDATE %onUpdate%"; |
|
39
|
|
|
$this->alterTableAddKey="ALTER TABLE %tableName% ADD %type% KEY (%pkFields%)"; |
|
40
|
|
|
$this->autoIncMask="ALTER TABLE %tableName% MODIFY %field% AUTO_INCREMENT, AUTO_INCREMENT=%value%"; |
|
41
|
|
|
$this->fieldTypes=["tinyint"=>0,"int"=>0,"decimal"=>0,"float"=>0,"double"=>0,"smallint"=>0,"mediumint"=>0,"bigint"=>0, |
|
42
|
|
|
"date"=>"NULL","time"=>"NULL","datetime"=>"CURRENT_TIMESTAMP","timestamp"=>"CURRENT_TIMESTAMP","year"=>"'0000'", |
|
43
|
|
|
"tinytext"=>"NULL","text"=>"NULL","mediumtext"=>"NULL","longtext"=>"NULL", |
|
44
|
|
|
"tinyblob"=>"NULL","blob"=>"NULL","mediumblob"=>"NULL","longblob"=>"NULL", |
|
45
|
|
|
"char"=>"NULL","varchar"=>"NULL","binary"=>"NULL","varbinary"=>"NULL", |
|
46
|
|
|
"enum"=>"''","set"=>"''" |
|
47
|
|
|
]; |
|
48
|
|
|
$this->defaultType="varchar(30)"; |
|
49
|
|
|
} |
|
50
|
|
|
|
|
51
|
|
|
public function createDatabase($name){ |
|
52
|
|
|
$script= $this->replaceMask("name", $name, $this->createDatabaseMask); |
|
53
|
|
|
return $this->addScript("head", $script); |
|
54
|
|
|
} |
|
55
|
|
|
|
|
56
|
|
|
public function selectDatabase($name){ |
|
57
|
|
|
$script= $this->replaceMask("name", $name, $this->selectDbMask); |
|
58
|
|
|
return $this->addScript("head", $script); |
|
59
|
|
|
} |
|
60
|
|
|
|
|
61
|
|
|
public function createTable($name,$fieldsAttributes,$attributes=["ENGINE=InnoDB","DEFAULT CHARSET=utf8"]){ |
|
62
|
|
|
$fields=$this->generateFields($fieldsAttributes); |
|
63
|
|
|
$attributes=\implode(" ", $attributes); |
|
64
|
|
|
$script=$this->replaceArrayMask(["name"=>$name,"fields"=>$fields,"attributes"=>$attributes], $this->createTableMask); |
|
65
|
|
|
return $this->addScript("body", $script); |
|
66
|
|
|
} |
|
67
|
|
|
|
|
68
|
|
|
public function addKey($tableName,$fieldNames,$type="PRIMARY"){ |
|
69
|
|
|
$pks=[]; |
|
70
|
|
|
foreach ($fieldNames as $fieldName){ |
|
71
|
|
|
$pks[]=$this->nameProtection.$fieldName.$this->nameProtection; |
|
72
|
|
|
} |
|
73
|
|
|
$script= $this->replaceArrayMask(["tableName"=>$tableName,"pkFields"=>\implode(",", $pks),"type"=>$type], $this->alterTableAddKey); |
|
74
|
|
|
return $this->addScript("before-constraints", $script); |
|
75
|
|
|
} |
|
76
|
|
|
|
|
77
|
|
|
public function addForeignKey($tableName,$fkFieldName,$referencesTableName,$referencesFieldName,$fkName=null,$onDelete="CASCADE",$onUpdate="NO ACTION"){ |
|
78
|
|
|
if(!isset($fkName)){ |
|
79
|
|
|
$fkName=$this->checkConstraintName("fk_".$tableName."_".$referencesTableName); |
|
80
|
|
|
} |
|
81
|
|
|
$script= $this->replaceArrayMask(["tableName"=>$tableName,"fkName"=>$fkName,"fkFieldName"=>$fkFieldName,"referencesTableName"=>$referencesTableName,"referencesFieldName"=>$referencesFieldName,"onDelete"=>$onDelete,"onUpdate"=>$onUpdate], $this->foreignKeyMask); |
|
82
|
|
|
return $this->addScript("constraints", $script); |
|
83
|
|
|
} |
|
84
|
|
|
|
|
85
|
|
|
public function addAutoInc($tableName,$fieldName,$value=1){ |
|
86
|
|
|
$script= $this->replaceArrayMask(["tableName"=>$tableName,"field"=>$fieldName,"value"=>$value], $this->autoIncMask); |
|
87
|
|
|
return $this->addScript("before-constraints", $script); |
|
88
|
|
|
} |
|
89
|
|
|
|
|
90
|
|
|
protected function addScript($key,$script){ |
|
91
|
|
|
if(!isset($this->sqlScript[$key])){ |
|
92
|
|
|
$this->sqlScript[$key]=[]; |
|
93
|
|
|
} |
|
94
|
|
|
$this->sqlScript[$key][]=$script; |
|
95
|
|
|
return $script; |
|
96
|
|
|
} |
|
97
|
|
|
|
|
98
|
|
|
protected function checkConstraintName($name){ |
|
99
|
|
|
if(\array_search($name, $this->constraintNames)){ |
|
100
|
|
|
$matches=[]; |
|
101
|
|
|
if (\preg_match('@([\s\S]*?)((?:\d)+)$@', $name,$matches)) { |
|
102
|
|
|
if(isset($matches[2])){ |
|
103
|
|
|
$nb=\intval($matches[2])+1; |
|
104
|
|
|
$name= $matches[1].$nb; |
|
105
|
|
|
} |
|
106
|
|
|
}else{ |
|
107
|
|
|
$name= $name."1"; |
|
108
|
|
|
} |
|
109
|
|
|
} |
|
110
|
|
|
$this->constraintNames[]=$name; |
|
111
|
|
|
return $name; |
|
112
|
|
|
} |
|
113
|
|
|
|
|
114
|
|
|
public function generateField($fieldAttributes){ |
|
115
|
|
|
$fieldAttributes=$this->checkFieldAttributes($fieldAttributes); |
|
116
|
|
|
return $this->replaceArrayMask($fieldAttributes,$this->fieldMask); |
|
117
|
|
|
} |
|
118
|
|
|
|
|
119
|
|
|
protected function checkFieldAttributes($fieldAttributes){ |
|
120
|
|
|
$result=$fieldAttributes; |
|
121
|
|
|
$type=$fieldAttributes["type"]; |
|
122
|
|
|
$existingType=false; |
|
123
|
|
|
$matches=[]; |
|
124
|
|
|
if (\preg_match($this->typeMatch, $type,$matches)) { |
|
125
|
|
|
if(isset($matches[1])){ |
|
126
|
|
|
$strType=$matches[1]; |
|
127
|
|
|
if(isset($this->fieldTypes[$strType])){ |
|
128
|
|
|
if(!isset($fieldAttributes["extra"]) || $fieldAttributes["extra"]=="") { |
|
129
|
|
|
$result["extra"]="DEFAULT ".$this->fieldTypes[$strType]; |
|
130
|
|
|
} |
|
131
|
|
|
$existingType=true; |
|
132
|
|
|
} |
|
133
|
|
|
} |
|
134
|
|
|
} |
|
135
|
|
|
if(!$existingType){ |
|
136
|
|
|
$result["type"]=$this->defaultType; |
|
137
|
|
|
} |
|
138
|
|
|
return $result; |
|
139
|
|
|
} |
|
140
|
|
|
|
|
141
|
|
|
protected function generateFields($fieldsAttributes){ |
|
142
|
|
|
$result=[]; |
|
143
|
|
|
foreach ($fieldsAttributes as $fieldAttribute){ |
|
144
|
|
|
$result[]=$this->generateField($fieldAttribute); |
|
145
|
|
|
} |
|
146
|
|
|
return \implode(",", $result); |
|
147
|
|
|
} |
|
148
|
|
|
|
|
149
|
|
|
protected function replaceMask($key,$value,$mask){ |
|
150
|
|
|
if(\strstr(\strtolower($key),"name")) |
|
151
|
|
|
$value=$this->nameProtection.$value.$this->nameProtection; |
|
152
|
|
|
return \str_replace("%".$key."%", $value, $mask); |
|
153
|
|
|
} |
|
154
|
|
|
|
|
155
|
|
|
protected function replaceArrayMask($keyValues,$mask){ |
|
156
|
|
|
foreach ($keyValues as $key=>$value){ |
|
157
|
|
|
$mask=$this->replaceMask($key, $value, $mask); |
|
158
|
|
|
} |
|
159
|
|
|
return $mask; |
|
160
|
|
|
} |
|
161
|
|
|
|
|
162
|
|
|
public function getSqlScript() { |
|
163
|
|
|
return $this->sqlScript; |
|
164
|
|
|
} |
|
165
|
|
|
|
|
166
|
|
|
public function addManyToMany($jointable,$targetEntity){ |
|
167
|
|
|
if(!isset($this->manyToManys[$jointable])){ |
|
168
|
|
|
$this->manyToManys[$jointable]=[]; |
|
169
|
|
|
} |
|
170
|
|
|
$this->manyToManys[$jointable][]=$targetEntity; |
|
171
|
|
|
} |
|
172
|
|
|
|
|
173
|
|
|
public function generateManyToManys(){ |
|
174
|
|
|
foreach ($this->manyToManys as $joinTable=>$targetEntities){ |
|
175
|
|
|
$this->generateManyToMany($joinTable, $targetEntities); |
|
176
|
|
|
} |
|
177
|
|
|
} |
|
178
|
|
|
|
|
179
|
|
|
protected function generateManyToMany($joinTable,$targetEntities){ |
|
180
|
|
|
$fields=[]; |
|
181
|
|
|
$fieldTypes=[]; |
|
182
|
|
|
$manyToOnes=[]; |
|
183
|
|
|
$invertedJoinColumns=[]; |
|
184
|
|
|
foreach ($targetEntities as $targetEntity){ |
|
185
|
|
|
$pk=OrmUtils::getFirstKey($targetEntity); |
|
186
|
|
|
$shortClassName=ClassUtils::getClassSimpleName($targetEntity); |
|
187
|
|
|
$fieldName=$pk.\ucfirst($shortClassName); |
|
188
|
|
|
$fields[]=$fieldName; |
|
189
|
|
|
$type=OrmUtils::getFieldType($targetEntity, $pk); |
|
190
|
|
|
$fieldTypes[$fieldName]=$type; |
|
191
|
|
|
$memberName=\lcfirst($shortClassName); |
|
192
|
|
|
$manyToOnes[]=$memberName; |
|
193
|
|
|
//$fields[]=$memberName; |
|
|
|
|
|
|
194
|
|
|
$invertedJoinColumns[$fieldName]=["member"=>$memberName,"className"=>$targetEntity]; |
|
195
|
|
|
} |
|
196
|
|
|
$metas=["#tableName"=>$joinTable,"#primaryKeys"=>$fields,"#nullable"=>[], |
|
197
|
|
|
"#notSerializable"=>[],"#fieldTypes"=>$fieldTypes,"#manyToOne"=>$manyToOnes, |
|
198
|
|
|
"#invertedJoinColumn"=>$invertedJoinColumns,"#oneToMany"=>[],"#joinTable"=>[], |
|
199
|
|
|
"#manyToMany"=>[],"#fieldNames"=>$fields |
|
200
|
|
|
]; |
|
201
|
|
|
$tableGenerator=new TableReversor(); |
|
202
|
|
|
$tableGenerator->init($metas); |
|
203
|
|
|
$tableGenerator->generateSQL($this); |
|
204
|
|
|
} |
|
205
|
|
|
|
|
206
|
|
|
public function __toString(){ |
|
207
|
|
|
$scripts=\array_merge($this->sqlScript["head"],$this->sqlScript["body"]); |
|
208
|
|
|
$scripts=\array_merge($scripts,$this->sqlScript["before-constraints"]); |
|
209
|
|
|
$scripts=\array_merge($scripts,$this->sqlScript["constraints"]); |
|
210
|
|
|
return \implode(";\n", $scripts); |
|
211
|
|
|
} |
|
212
|
|
|
} |
|
213
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.