2222class SchemaAnalyzer
2323{
2424 private static $ WEIGHT_FK = 1 ;
25+ private static $ WEIGHT_INHERITANCE_FK = 0.1 ;
2526 private static $ WEIGHT_JOINTURE_TABLE = 1.5 ;
2627
2728 const WEIGHT_IMPORTANT = 0.75 ;
@@ -49,13 +50,15 @@ class SchemaAnalyzer
4950 private $ cachePrefix ;
5051
5152 /**
52- * Nested arrays containing table => column => cost
53+ * Nested arrays containing table => column => cost.
54+ *
5355 * @var float[][]
5456 */
5557 private $ alteredCosts = [];
5658
5759 /**
58- * Array containing table cost
60+ * Array containing table cost.
61+ *
5962 * @var float[]
6063 */
6164 private $ alteredTableCosts = [];
@@ -124,7 +127,11 @@ private function isJunctionTable(Table $table)
124127 return false ;
125128 }
126129
127- $ pkColumns = $ table ->getPrimaryKeyColumns ();
130+ if ($ table ->hasPrimaryKey ()) {
131+ $ pkColumns = $ table ->getPrimaryKeyColumns ();
132+ } else {
133+ $ pkColumns = [];
134+ }
128135
129136 if (count ($ pkColumns ) == 1 && count ($ columns ) == 2 ) {
130137 return false ;
@@ -170,14 +177,9 @@ private function isJunctionTable(Table $table)
170177 */
171178 public function getShortestPath ($ fromTable , $ toTable )
172179 {
173- $ cacheKey = $ this ->cachePrefix .'_shortest_ ' .$ fromTable .'``` ' .$ toTable ;
174- $ path = $ this ->cache ->fetch ($ cacheKey );
175- if ($ path === false ) {
176- $ path = $ this ->getShortestPathWithoutCache ($ fromTable , $ toTable );
177- $ this ->cache ->save ($ cacheKey , $ path );
178- }
179-
180- return $ path ;
180+ return $ this ->fromCache ($ this ->cachePrefix .'_shortest_ ' .$ fromTable .'``` ' .$ toTable , function () use ($ fromTable , $ toTable ) {
181+ return $ this ->getShortestPathWithoutCache ($ fromTable , $ toTable );
182+ });
181183 }
182184
183185 /**
@@ -255,8 +257,10 @@ private function buildSchemaGraph()
255257 foreach ($ table ->getForeignKeys () as $ fk ) {
256258 // Create an undirected edge, with weight = 1
257259 $ edge = $ graph ->getVertex ($ table ->getName ())->createEdge ($ graph ->getVertex ($ fk ->getForeignTableName ()));
258- if (isset ($ this ->alteredCosts [$ fk ->getLocalTable ()->getName ()][implode (', ' ,$ fk ->getLocalColumns ())])) {
259- $ cost = $ this ->alteredCosts [$ fk ->getLocalTable ()->getName ()][implode (', ' ,$ fk ->getLocalColumns ())];
260+ if (isset ($ this ->alteredCosts [$ fk ->getLocalTable ()->getName ()][implode (', ' , $ fk ->getLocalColumns ())])) {
261+ $ cost = $ this ->alteredCosts [$ fk ->getLocalTable ()->getName ()][implode (', ' , $ fk ->getLocalColumns ())];
262+ } elseif ($ this ->isInheritanceRelationship ($ fk )) {
263+ $ cost = self ::$ WEIGHT_INHERITANCE_FK ;
260264 } else {
261265 $ cost = self ::$ WEIGHT_FK ;
262266 }
@@ -386,21 +390,25 @@ private function getTextualPath(array $path, Vertex $startVertex)
386390 *
387391 * @param string $tableName
388392 * @param string $columnName
389- * @param float $cost
393+ * @param float $cost
394+ *
390395 * @return $this
391396 */
392- public function setForeignKeyCost ($ tableName , $ columnName , $ cost ) {
397+ public function setForeignKeyCost ($ tableName , $ columnName , $ cost )
398+ {
393399 $ this ->alteredCosts [$ tableName ][$ columnName ] = $ cost ;
394400 }
395401
396402 /**
397403 * Sets the cost modifier of a table.
398404 *
399405 * @param string $tableName
400- * @param float $cost
406+ * @param float $cost
407+ *
401408 * @return $this
402409 */
403- public function setTableCostModifier ($ tableName , $ cost ) {
410+ public function setTableCostModifier ($ tableName , $ cost )
411+ {
404412 $ this ->alteredTableCosts [$ tableName ] = $ cost ;
405413 }
406414
@@ -409,7 +417,8 @@ public function setTableCostModifier($tableName, $cost) {
409417 *
410418 * @param array<string, float> $tableCosts The key is the table name, the value is the cost modifier.
411419 */
412- public function setTableCostModifiers (array $ tableCosts ) {
420+ public function setTableCostModifiers (array $ tableCosts )
421+ {
413422 $ this ->alteredTableCosts = $ tableCosts ;
414423 }
415424
@@ -418,7 +427,129 @@ public function setTableCostModifiers(array $tableCosts) {
418427 *
419428 * @param array<string, array<string, float>> $fkCosts First key is the table name, second key is the column name, the value is the cost.
420429 */
421- public function setForeignKeyCosts (array $ fkCosts ) {
430+ public function setForeignKeyCosts (array $ fkCosts )
431+ {
422432 $ this ->alteredCosts = $ fkCosts ;
423433 }
434+
435+ /**
436+ * Returns true if this foreign key represents an inheritance relationship,
437+ * i.e. if this foreign key is based on a primary key.
438+ *
439+ * @param ForeignKeyConstraint $fk
440+ *
441+ * @return true
442+ */
443+ private function isInheritanceRelationship (ForeignKeyConstraint $ fk )
444+ {
445+ if (!$ fk ->getLocalTable ()->hasPrimaryKey ()) {
446+ return false ;
447+ }
448+ $ fkColumnNames = $ fk ->getLocalColumns ();
449+ $ pkColumnNames = $ fk ->getLocalTable ()->getPrimaryKeyColumns ();
450+
451+ sort ($ fkColumnNames );
452+ sort ($ pkColumnNames );
453+
454+ return $ fkColumnNames == $ pkColumnNames ;
455+ }
456+
457+ /**
458+ * If this table is pointing to a parent table (if its primary key is a foreign key pointing on another table),
459+ * this function will return the pointed table.
460+ * This function will return null if there is no parent table.
461+ *
462+ * @param string $tableName
463+ *
464+ * @return string|null
465+ */
466+ public function getParentTable ($ tableName )
467+ {
468+ return $ this ->fromCache ($ this ->cachePrefix .'_parent_ ' .$ tableName , function () use ($ tableName ) {
469+ return $ this ->getParentTableWithoutCache ($ tableName );
470+ });
471+ }
472+
473+ /**
474+ * If this table is pointing to a parent table (if its primary key is a foreign key pointing on another table),
475+ * this function will return the pointed table.
476+ * This function will return null if there is no parent table.
477+ *
478+ * @param string $tableName
479+ *
480+ * @return string|null
481+ */
482+ private function getParentTableWithoutCache ($ tableName )
483+ {
484+ $ table = $ this ->getSchema ()->getTable ($ tableName );
485+ foreach ($ table ->getForeignKeys () as $ fk ) {
486+ if ($ this ->isInheritanceRelationship ($ fk )) {
487+ return $ fk ->getForeignTableName ();
488+ }
489+ }
490+
491+ return ;
492+ }
493+
494+ /**
495+ * If this table is pointed by children tables (if other child tables have a primary key that is also a
496+ * foreign key to this table), this function will return the list of child tables.
497+ * This function will return an empty array if there are no children tables.
498+ *
499+ * @param string $tableName
500+ *
501+ * @return string[]
502+ */
503+ public function getChildrenTables ($ tableName )
504+ {
505+ return $ this ->fromCache ($ this ->cachePrefix .'_children_ ' .$ tableName , function () use ($ tableName ) {
506+ return $ this ->getChildrenTablesWithoutCache ($ tableName );
507+ });
508+ }
509+
510+ /**
511+ * If this table is pointed by children tables (if other child tables have a primary key that is also a
512+ * foreign key to this table), this function will return the list of child tables.
513+ * This function will return an empty array if there are no children tables.
514+ *
515+ * @param string $tableName
516+ *
517+ * @return string[]
518+ */
519+ private function getChildrenTablesWithoutCache ($ tableName )
520+ {
521+ $ schema = $ this ->getSchema ();
522+ $ children = [];
523+ foreach ($ schema ->getTables () as $ table ) {
524+ if ($ table ->getName () === $ tableName ) {
525+ continue ;
526+ }
527+ foreach ($ table ->getForeignKeys () as $ fk ) {
528+ if ($ fk ->getForeignTableName () === $ tableName && $ this ->isInheritanceRelationship ($ fk )) {
529+ $ children [] = $ fk ->getLocalTableName ();
530+ }
531+ }
532+ }
533+
534+ return $ children ;
535+ }
536+
537+ /**
538+ * Returns an item from cache or computes it using $closure and puts it in cache.
539+ *
540+ * @param string $key
541+ * @param callable $closure
542+ *
543+ * @return mixed
544+ */
545+ private function fromCache ($ key , callable $ closure )
546+ {
547+ $ item = $ this ->cache ->fetch ($ key );
548+ if ($ item === false ) {
549+ $ item = $ closure ();
550+ $ this ->cache ->save ($ key , $ item );
551+ }
552+
553+ return $ item ;
554+ }
424555}
0 commit comments