Permalink
Browse files

Merge pull request #142 from fzaninotto/buildtime_validation

[generator] Introducing buildtime schema validation.
  • Loading branch information...
2 parents ce5b28f + 54b1941 commit 22e6a72c0f7966cda3465057ec2662afc9fecb28 @willdurand willdurand committed Oct 13, 2011
@@ -375,15 +375,17 @@ public function addTable($data)
{
if ($data instanceof Table) {
$tbl = $data; // alias
- $tbl->setDatabase($this);
- if ($tbl->getSchema() === null) $tbl->setSchema($this->getSchema());
if (isset($this->tablesByName[$tbl->getName()])) {
- throw new EngineException("Duplicate table declared: " . $tbl->getName());
+ throw new EngineException(sprintf('Table "%s" declared twice', $tbl->getName()));
+ }
+ $tbl->setDatabase($this);
+ if ($tbl->getSchema() === null) {
+ $tbl->setSchema($this->getSchema());
}
$this->tableList[] = $tbl;
$this->tablesByName[$tbl->getName()] = $tbl;
$this->tablesByLowercaseName[strtolower($tbl->getName())] = $tbl;
- $this->tablesByPhpName[ $tbl->getPhpName() ] = $tbl;
+ $this->tablesByPhpName[$tbl->getPhpName()] = $tbl;
if (strpos($tbl->getNamespace(), '\\') === 0) {
$tbl->setNamespace(substr($tbl->getNamespace(), 1));
} elseif ($namespace = $this->getNamespace()) {
@@ -412,12 +412,6 @@ public function doFinalInitialization()
if ($this->getIdMethod() === IDMethod::NATIVE && !$anyAutoInc) {
$this->setIdMethod(IDMethod::NO_ID_METHOD);
}
-
- // If there is no PK, then throw an error. Propel requires primary keys.
- if (!$this->hasPrimaryKey()) {
- throw new EngineException(sprintf('Table "%s" does not have a primary key defined. Propel requires all tables to have a primary key.', $this->getName()));
- }
-
}
/**
@@ -686,6 +680,9 @@ public function addColumn($data)
{
if ($data instanceof Column) {
$col = $data;
+ if (isset($this->columnsByName[$col->getName()])) {
+ throw new EngineException(sprintf('Column "%s" declared twice in table "%s"', $col->getName(), $this->getName()));
+ }
$col->setTable($this);
if ($col->isInheritance()) {
$this->inheritanceColumn = $col;
@@ -14,6 +14,7 @@
include_once 'model/AppData.php';
include_once 'model/Database.php';
include_once 'builder/util/XmlToAppData.php';
+include_once 'util/PropelSchemaValidator.php';
/**
* An abstract base Propel task to perform work related to the XML schema file.
@@ -476,6 +477,15 @@ protected function loadDataModels()
$ad->doFinalInitialization();
}
+ if ($this->validate) {
+ foreach ($this->dataModels as $dataModel) {
+ $validator = new PropelSchemaValidator($dataModel);
+ if (!$validator->validate()) {
+ throw new EngineException(sprintf("The database schema contains errors:\n - %s", join("\n - ", $validator->getErrors())));
+ }
+ }
+ }
+
$this->dataModelsLoaded = true;
}
@@ -0,0 +1,91 @@
+<?php
+
+/**
+ * This file is part of the Propel package.
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @license MIT License
+ */
+
+/**
+ * Service class for validating XML schemas.
+ * Only implements validation rules that cannot be implemented in XSD.
+ *
+ * @example Basic usage:
+ * <code>
+ * $validator = new PropelSchemaValidator($appData);
+ * if (!$validator->validate()) {
+ * throw new Exception("Invalid schema:\n" . join("\n", $validator->getErrors()));
+ * }
+ * </code>
+ *
+ * @package propel.generator.util
+ * @author François Zaninotto
+ */
+class PropelSchemaValidator
+{
+ protected $appData;
+ protected $errors = array();
+
+ public function __construct(AppData $appData)
+ {
+ $this->appData = $appData;
+ }
+
+ /**
+ * @return boolean true if valid, false otherwise
+ */
+ public function validate()
+ {
+ foreach ($this->appData->getDatabases() as $database) {
+ $this->validateDatabaseTables($database);
+ }
+ return count($this->errors) == 0;
+ }
+
+ protected function validateDatabaseTables(Database $database)
+ {
+ $phpNames = array();
+ foreach ($database->getTables() as $table) {
+ if (in_array($table->getPhpName(), $phpNames)) {
+ $this->errors[] = sprintf('Table "%s" declares a phpName already used in another table', $table->getName());
+ }
+ $phpNames[]= $table->getPhpName();
+ $this->validateTableAttributes($table);
+ $this->validateTableColumns($table);
+ }
+ }
+
+ protected function validateTableAttributes(Table $table)
+ {
+ $reservedTableNames = array('table_name');
+ $tableName = strtolower($table->getName());
+ if (in_array($tableName, $reservedTableNames)) {
+ $this->errors[] = sprintf('Table "%s" uses a reserved keyword as name', $table->getName());
+ }
+ }
+
+ protected function validateTableColumns(Table $table)
+ {
+ if (!$table->hasPrimaryKey() && !$table->isSkipSql()) {
+ $this->errors[] = sprintf('Table "%s" does not have a primary key defined. Propel requires all tables to have a primary key.', $table->getName());
+ }
+ $phpNames = array();
+ foreach ($table->getColumns() as $column) {
+ if (in_array($column->getPhpName(), $phpNames)) {
+ $this->errors[] = sprintf('Column "%s" declares a phpName already used in table "%s"', $column->getName(), $table->getName());
+ }
+ $phpNames[]= $column->getPhpName();
+ }
+ }
+
+ /**
+ * @return array A list of error messages
+ */
+ public function getErrors()
+ {
+ return $this->errors;
+ }
+
+}
@@ -0,0 +1,111 @@
+<?php
+
+/**
+ * This file is part of the Propel package.
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @license MIT License
+ */
+
+require_once dirname(__FILE__) . '/../../../../generator/lib/util/PropelSchemaValidator.php';
+require_once dirname(__FILE__) . '/../../../../generator/lib/util/PropelQuickBuilder.php';
+require_once dirname(__FILE__) . '/../../../../generator/lib/model/AppData.php';
+
+/**
+ *
+ * @package generator.util
+ */
+class SchemaValidatorTest extends PHPUnit_Framework_TestCase
+{
+ protected function getAppDataForTable($table)
+ {
+ $database = new Database();
+ $database->addTable($table);
+ $appData = new AppData();
+ $appData->addDatabase($database);
+ return $appData;
+ }
+
+ public function testValidateReturnsTrueForEmptySchema()
+ {
+ $schema = new AppData();
+ $validator = new PropelSchemaValidator($schema);
+ $this->assertTrue($validator->validate());
+ }
+
+ public function testValidateReturnsTrueForValidSchema()
+ {
+ $schema = <<<EOF
+<database name="bookstore">
+ <table name="book">
+ <column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
+ <column name="title" type="VARCHAR" size="100" primaryString="true" />
+ </table>
+</database>
+EOF;
+ $builder = new PropelQuickBuilder();
+ $builder->setSchema($schema);
+ $database = $builder->getDatabase();
+ $appData = new AppData();
+ $appData->addDatabase($database);
+ $validator = new PropelSchemaValidator($appData);
+ $this->assertTrue($validator->validate());
+ }
+
+ public function testValidateReturnsFalseWhenTwoTablesHaveSamePhpName()
+ {
+ $table1 = new Table('foo');
+ $table2 = new Table('bar');
+ $table2->setPhpName('Foo');
+ $database = new Database();
+ $database->addTable($table1);
+ $database->addTable($table2);
+ $appData = new AppData();
+ $appData->addDatabase($database);
+ $validator = new PropelSchemaValidator($appData);
+ $this->assertFalse($validator->validate());
+ $this->assertContains('Table "bar" declares a phpName already used in another table', $validator->getErrors());
+ }
+
+ public function testValidateReturnsFalseWhenTableHasNoPk()
+ {
+ $appData = $this->getAppDataForTable(new Table('foo'));
+ $validator = new PropelSchemaValidator($appData);
+ $this->assertFalse($validator->validate());
+ $this->assertContains('Table "foo" does not have a primary key defined. Propel requires all tables to have a primary key.', $validator->getErrors());
+ }
+
+ public function testValidateReturnsTrueWhenTableHasNoPkButIsAView()
+ {
+ $table = new Table('foo');
+ $table->setSkipSql(true);
+ $appData = $this->getAppDataForTable($table);
+ $validator = new PropelSchemaValidator($appData);
+ $this->assertTrue($validator->validate());
+ }
+
+ public function testValidateReturnsFalseWhenTableHasAReservedName()
+ {
+ $appData = $this->getAppDataForTable(new Table('TABLE_NAME'));
+ $validator = new PropelSchemaValidator($appData);
+ $this->assertFalse($validator->validate());
+ $this->assertContains('Table "TABLE_NAME" uses a reserved keyword as name', $validator->getErrors());
+ }
+
+ public function testValidateReturnsFalseWhenTwoColumnssHaveSamePhpName()
+ {
+ $column1 = new Column('foo');
+ $column2 = new Column('bar');
+ $column2->setPhpName('Foo');
+ $table = new Table('foo_table');
+ $table->addColumn($column1);
+ $table->addColumn($column2);
+ $appData = $this->getAppDataForTable($table);
+ $validator = new PropelSchemaValidator($appData);
+ $this->assertFalse($validator->validate());
+ $this->assertContains('Column "bar" declares a phpName already used in table "foo_table"', $validator->getErrors());
+ }
+
+
+}

0 comments on commit 22e6a72

Please sign in to comment.