Skip to content

Commit 56d1952

Browse files
author
Marc TEYSSIER
committed
Merge pull request #1 from moufmouf/1.0
Modifying class signature to use Doctrine cache. The SchemaManager can now natively cache results.
2 parents 7669fd9 + 5ee2a17 commit 56d1952

File tree

5 files changed

+169
-25
lines changed

5 files changed

+169
-25
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,14 @@ between minor versions.
3131

3232
## Detecting junction tables
3333

34-
The starting point is always a DBAL Schema. Pass the schema to SchemaAnalyzer, and then, simply call the functions.
34+
The starting point is always a DBAL Schema. Pass the schema manager to SchemaAnalyzer, and then, simply call the functions.
3535

3636
```php
3737
// $conn is the DBAL connection.
38-
$schema = $conn->getSchemaManager()->createSchema();
38+
$schemaAnalyzer = new SchemaAnalyzer($conn->getSchemaManager());
3939

4040
// Let's detect all junctions tables
41-
$tables = $schema->detectJunctionTables();
41+
$tables = $schemaAnalyzer->detectJunctionTables();
4242
// This will return an array of Doctrine\DBAL\Schema\Table objects
4343
```
4444

@@ -59,10 +59,10 @@ Internals:
5959

6060
```php
6161
// $conn is the DBAL connection.
62-
$schema = $conn->getSchemaManager()->createSchema();
62+
$schemaAnalyzer = new SchemaAnalyzer($conn->getSchemaManager());
6363

6464
// Let's detect the shortest path between 2 tables:
65-
$fks = $schema->getShortestPath("users", "rights");
65+
$fks = $schemaAnalyzer->getShortestPath("users", "rights");
6666
// This will return an array of Doctrine\DBAL\Schema\ForeignKeyConstraint objects
6767
```
6868

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
},
1111
"autoload-dev": {
1212
"psr-4": {
13-
"Mouf\\Database\\SchemaAnalyzer\\": "src/"
13+
"Mouf\\Database\\SchemaAnalyzer\\": "tests/"
1414
}
1515
},
1616
"require": {

src/SchemaAnalyzer.php

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
namespace Mouf\Database\SchemaAnalyzer;
44

5+
use Doctrine\Common\Cache\Cache;
6+
use Doctrine\Common\Cache\VoidCache;
7+
use Doctrine\DBAL\Schema\AbstractSchemaManager;
58
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
69
use Doctrine\DBAL\Schema\Schema;
710
use Doctrine\DBAL\Schema\Table;
@@ -21,17 +24,43 @@ class SchemaAnalyzer
2124
private static $WEIGHT_FK = 1;
2225
private static $WEIGHT_JOINTURE_TABLE = 1.5;
2326

27+
/**
28+
* @var AbstractSchemaManager
29+
*/
30+
private $schemaManager;
31+
2432
/**
2533
* @var Schema
2634
*/
2735
private $schema;
2836

2937
/**
30-
* @param Schema $schema
38+
* @var Cache
39+
*/
40+
private $cache;
41+
42+
/**
43+
* @var string
3144
*/
32-
public function __construct(Schema $schema)
45+
private $cachePrefix;
46+
47+
/**
48+
* @param AbstractSchemaManager $schemaManager
49+
* @param Cache|null $cache The Doctrine cache service to use to cache results (optional)
50+
* @param string|null $schemaCacheKey The unique identifier for the schema manager. Compulsory if cache is set.
51+
*/
52+
public function __construct(AbstractSchemaManager $schemaManager, Cache $cache = null, $schemaCacheKey = null)
3353
{
34-
$this->schema = $schema;
54+
$this->schemaManager = $schemaManager;
55+
if (empty($schemaCacheKey) && $cache) {
56+
throw new SchemaAnalyzerException('You must provide a schema cache key if you configure SchemaAnalyzer with cache support.');
57+
}
58+
if ($cache) {
59+
$this->cache = $cache;
60+
} else {
61+
$this->cache = new VoidCache();
62+
}
63+
$this->cachePrefix = $schemaCacheKey;
3564
}
3665

3766
/**
@@ -45,7 +74,13 @@ public function __construct(Schema $schema)
4574
*/
4675
public function detectJunctionTables()
4776
{
48-
return array_filter($this->schema->getTables(), [$this, 'isJunctionTable']);
77+
$junctionTablesKey = $this->cachePrefix."_junctiontables";
78+
$junctionTables = $this->cache->fetch($junctionTablesKey);
79+
if ($junctionTables === false) {
80+
$junctionTables = array_filter($this->getSchema()->getTables(), [$this, 'isJunctionTable']);
81+
$this->cache->save($junctionTablesKey, $junctionTables);
82+
}
83+
return $junctionTables;
4984
}
5085

5186
/**
@@ -109,10 +144,29 @@ private function isJunctionTable(Table $table)
109144
*
110145
* @param string $fromTable
111146
* @param string $toTable
112-
*
113-
* @return ForeignKeyConstraint[]
147+
* @return \Doctrine\DBAL\Schema\ForeignKeyConstraint[]
148+
* @throws SchemaAnalyzerException
114149
*/
115150
public function getShortestPath($fromTable, $toTable)
151+
{
152+
$cacheKey = $this->cachePrefix."_shortest_".$fromTable."```".$toTable;
153+
$path = $this->cache->fetch($cacheKey);
154+
if ($path === false) {
155+
$path = $this->getShortestPathWithoutCache($fromTable, $toTable);
156+
$this->cache->save($cacheKey, $path);
157+
}
158+
return $path;
159+
}
160+
161+
/**
162+
* Get the shortest path between 2 tables.
163+
*
164+
* @param string $fromTable
165+
* @param string $toTable
166+
* @return \Doctrine\DBAL\Schema\ForeignKeyConstraint[]
167+
* @throws SchemaAnalyzerException
168+
*/
169+
private function getShortestPathWithoutCache($fromTable, $toTable)
116170
{
117171
$graph = $this->buildSchemaGraph();
118172

@@ -161,12 +215,12 @@ private function buildSchemaGraph()
161215
$graph = new Graph();
162216

163217
// First, let's create all the vertex
164-
foreach ($this->schema->getTables() as $table) {
218+
foreach ($this->getSchema()->getTables() as $table) {
165219
$graph->createVertex($table->getName());
166220
}
167221

168222
// Then, let's create all the edges
169-
foreach ($this->schema->getTables() as $table) {
223+
foreach ($this->getSchema()->getTables() as $table) {
170224
foreach ($table->getForeignKeys() as $fk) {
171225
// Create an undirected edge, with weight = 1
172226
$edge = $graph->getVertex($table->getName())->createEdge($graph->getVertex($fk->getForeignTableName()));
@@ -189,4 +243,21 @@ private function buildSchemaGraph()
189243

190244
return $graph;
191245
}
246+
247+
/**
248+
* Returns the schema (from the schema manager or the cache if needed)
249+
* @return Schema
250+
*/
251+
private function getSchema() {
252+
if ($this->schema === null) {
253+
$schemaKey = $this->cachePrefix."_schema";
254+
$this->schema = $this->cache->fetch($schemaKey);
255+
if (empty($this->schema)) {
256+
$this->schema = $this->schemaManager->createSchema();
257+
$this->cache->save($schemaKey, $this->schema);
258+
}
259+
}
260+
return $this->schema;
261+
}
262+
192263
}

tests/SchemaAnalyzerTest.php

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22
namespace Mouf\Database\SchemaAnalyzer;
33

4+
use Doctrine\Common\Cache\ArrayCache;
45
use Doctrine\DBAL\Schema\Schema;
56

67
class SchemaAnalyzerTest extends \PHPUnit_Framework_TestCase
@@ -22,7 +23,7 @@ private function getBaseSchema() {
2223
return $schema;
2324
}
2425

25-
public function testJointureTableDetectionWith2Columns() {
26+
private function getCompleteSchemaManager() {
2627
$schema = $this->getBaseSchema();
2728

2829
$role_right = $schema->createTable("role_right");
@@ -32,7 +33,13 @@ public function testJointureTableDetectionWith2Columns() {
3233
$role_right->addForeignKeyConstraint($schema->getTable('right'), array("right_id"), array("id"), array("onUpdate" => "CASCADE"));
3334
$role_right->setPrimaryKey(["role_id", "right_id"]);
3435

35-
$schemaAnalyzer = new SchemaAnalyzer($schema);
36+
return new StubSchemaManager($schema);
37+
}
38+
39+
public function testJointureTableDetectionWith2Columns() {
40+
$schemaManager = $this->getCompleteSchemaManager();
41+
42+
$schemaAnalyzer = new SchemaAnalyzer($schemaManager);
3643
$junctionTables = $schemaAnalyzer->detectJunctionTables();
3744

3845
$this->assertCount(1, $junctionTables);
@@ -54,7 +61,7 @@ public function testJointureTableDetectionWith3ColumnsNoPrimaryKey() {
5461
$role_right->addForeignKeyConstraint($schema->getTable('right'), array("right_id"), array("id"), array("onUpdate" => "CASCADE"));
5562
$role_right->setPrimaryKey(["role_id", "right_id"]);
5663

57-
$schemaAnalyzer = new SchemaAnalyzer($schema);
64+
$schemaAnalyzer = new SchemaAnalyzer(new StubSchemaManager($schema));
5865
$junctionTables = $schemaAnalyzer->detectJunctionTables();
5966

6067
$this->assertCount(0, $junctionTables);
@@ -72,7 +79,7 @@ public function testJointureTableDetectionWith3Columns() {
7279
$role_right->addForeignKeyConstraint($schema->getTable('right'), array("right_id"), array("id"), array("onUpdate" => "CASCADE"));
7380
$role_right->setPrimaryKey(["id"]);
7481

75-
$schemaAnalyzer = new SchemaAnalyzer($schema);
82+
$schemaAnalyzer = new SchemaAnalyzer(new StubSchemaManager($schema));
7683
$junctionTables = $schemaAnalyzer->detectJunctionTables();
7784

7885
$this->assertCount(1, $junctionTables);
@@ -94,7 +101,7 @@ public function testJointureTableDetectionWith3ColumnsNoAutoincrement() {
94101
$role_right->addForeignKeyConstraint($schema->getTable('right'), array("right_id"), array("id"), array("onUpdate" => "CASCADE"));
95102
$role_right->setPrimaryKey(["id"]);
96103

97-
$schemaAnalyzer = new SchemaAnalyzer($schema);
104+
$schemaAnalyzer = new SchemaAnalyzer(new StubSchemaManager($schema));
98105
$junctionTables = $schemaAnalyzer->detectJunctionTables();
99106

100107
$this->assertCount(0, $junctionTables);
@@ -113,7 +120,7 @@ public function testJointureTableDetectionWith4Columns() {
113120
$role_right->addForeignKeyConstraint($schema->getTable('right'), array("right_id"), array("id"), array("onUpdate" => "CASCADE"));
114121
$role_right->setPrimaryKey(["id"]);
115122

116-
$schemaAnalyzer = new SchemaAnalyzer($schema);
123+
$schemaAnalyzer = new SchemaAnalyzer(new StubSchemaManager($schema));
117124
$junctionTables = $schemaAnalyzer->detectJunctionTables();
118125

119126
$this->assertCount(0, $junctionTables);
@@ -130,7 +137,7 @@ public function testJointureTableDetectionWith2ColumnsAndOnePk() {
130137
$role_right->addForeignKeyConstraint($schema->getTable('right'), array("right_id"), array("id"), array("onUpdate" => "CASCADE"));
131138
$role_right->setPrimaryKey(["role_id"]);
132139

133-
$schemaAnalyzer = new SchemaAnalyzer($schema);
140+
$schemaAnalyzer = new SchemaAnalyzer(new StubSchemaManager($schema));
134141
$junctionTables = $schemaAnalyzer->detectJunctionTables();
135142

136143
$this->assertCount(0, $junctionTables);
@@ -148,7 +155,7 @@ public function testJointureTableDetectionWith2ColumnsWith2FkOnOneCol() {
148155
$role_right->addForeignKeyConstraint($schema->getTable('right'), array("right_id"), array("id"), array("onUpdate" => "CASCADE"));
149156
$role_right->setPrimaryKey(["role_id", "right_id"]);
150157

151-
$schemaAnalyzer = new SchemaAnalyzer($schema);
158+
$schemaAnalyzer = new SchemaAnalyzer(new StubSchemaManager($schema));
152159
$junctionTables = $schemaAnalyzer->detectJunctionTables();
153160

154161
$this->assertCount(0, $junctionTables);
@@ -166,7 +173,7 @@ public function testJointureTableDetectionWith3ColumnsWithPkIsFk() {
166173
$role_right->addForeignKeyConstraint($schema->getTable('right'), array("right_id"), array("id"), array("onUpdate" => "CASCADE"));
167174
$role_right->setPrimaryKey(["id"]);
168175

169-
$schemaAnalyzer = new SchemaAnalyzer($schema);
176+
$schemaAnalyzer = new SchemaAnalyzer(new StubSchemaManager($schema));
170177
$junctionTables = $schemaAnalyzer->detectJunctionTables();
171178

172179
$this->assertCount(0, $junctionTables);
@@ -182,7 +189,7 @@ public function testShortestPathInJointure() {
182189
$role_right->addForeignKeyConstraint($schema->getTable('right'), array("right_id"), array("id"), array("onUpdate" => "CASCADE"));
183190
$role_right->setPrimaryKey(["role_id", "right_id"]);
184191

185-
$schemaAnalyzer = new SchemaAnalyzer($schema);
192+
$schemaAnalyzer = new SchemaAnalyzer(new StubSchemaManager($schema));
186193

187194
$fks = $schemaAnalyzer->getShortestPath("role", "right");
188195

@@ -211,7 +218,7 @@ public function testShortestPathInLine() {
211218
$role_right->addForeignKeyConstraint($schema->getTable('right'), array("right_id"), array("id"), array("onUpdate" => "CASCADE"));
212219
$role_right->setPrimaryKey(["role_id", "right_id"]);
213220

214-
$schemaAnalyzer = new SchemaAnalyzer($schema);
221+
$schemaAnalyzer = new SchemaAnalyzer(new StubSchemaManager($schema));
215222

216223
$fks = $schemaAnalyzer->getShortestPath("role", "role_right");
217224

@@ -225,5 +232,32 @@ public function testShortestPathInLine() {
225232
$this->assertEquals("role_right", $fks[0]->getLocalTable()->getName());
226233
$this->assertEquals("role", $fks[0]->getForeignTableName());
227234
}
235+
236+
/**
237+
* @expectedException \Mouf\Database\SchemaAnalyzer\SchemaAnalyzerException
238+
*/
239+
public function testWrongConstructor() {
240+
$schema = $this->getBaseSchema();
241+
new SchemaAnalyzer(new StubSchemaManager($schema), new ArrayCache());
242+
}
243+
244+
public function testCache() {
245+
$cache = new ArrayCache();
246+
247+
$schemaManager = $this->getCompleteSchemaManager();
248+
249+
$schemaAnalyzer = new SchemaAnalyzer($schemaManager, $cache, "mykey");
250+
$schemaAnalyzer->detectJunctionTables();
251+
252+
$this->assertNotFalse($cache->fetch('mykey_schema'));
253+
$this->assertNotFalse($cache->fetch('mykey_junctiontables'));
254+
$r1 = $schemaAnalyzer->getShortestPath("role_right", "role");
255+
$r2 = $schemaAnalyzer->getShortestPath("role_right", "role");
256+
$this->assertTrue($r1 === $r2);
257+
258+
$r1 = $this->assertNotFalse($cache->fetch('mykey_shortest_role_right```role'));
259+
$r2 = $this->assertNotFalse($cache->fetch('mykey_shortest_role_right```role'));
260+
$this->assertTrue($r1 === $r2);
261+
}
228262
}
229263

tests/StubSchemaManager.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
namespace Mouf\Database\SchemaAnalyzer;
3+
4+
use Doctrine\DBAL\Schema\AbstractSchemaManager;
5+
use Doctrine\DBAL\Schema\Schema;
6+
7+
/**
8+
* A stub for schema manager that simply returns the schema we are providing.
9+
*/
10+
class StubSchemaManager extends AbstractSchemaManager
11+
{
12+
private $schema;
13+
14+
public function __construct(Schema $schema)
15+
{
16+
$this->schema = $schema;
17+
}
18+
19+
/**
20+
* Creates a schema instance for the current database.
21+
*
22+
* @return \Doctrine\DBAL\Schema\Schema
23+
*/
24+
public function createSchema()
25+
{
26+
return $this->schema;
27+
}
28+
29+
/**
30+
* Gets Table Column Definition.
31+
*
32+
* @param array $tableColumn
33+
*
34+
* @return \Doctrine\DBAL\Schema\Column
35+
*/
36+
protected function _getPortableTableColumnDefinition($tableColumn)
37+
{
38+
}
39+
}

0 commit comments

Comments
 (0)