From cc2e2507194298b89ad7ca7a6304423d86e3a87b Mon Sep 17 00:00:00 2001 From: Hamish Friedlander Date: Wed, 29 Aug 2012 15:08:26 +1200 Subject: [PATCH 1/2] NEW Allow querying if a field exists on a table --- model/Database.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/model/Database.php b/model/Database.php index d76f73853..7e32cd114 100644 --- a/model/Database.php +++ b/model/Database.php @@ -143,7 +143,7 @@ abstract class SS_Database { * Returns true if the given table exists in the database */ abstract function hasTable($tableName); - + /** * Returns the enum values available on the given field */ @@ -439,6 +439,18 @@ abstract class SS_Database { } } + /** + * Return true if the table exists and already has a the field specified + * @param string $tableName - The table to check + * @param string $fieldName - The field to check + * @return bool - True if the table exists and the field exists on the table + */ + function hasField($tableName, $fieldName) { + if (!$this->hasTable($tableName)) return false; + $fields = $this->fieldList($tableName); + return array_key_exists($fieldName, $fields); + } + /** * Generate the given field on the table, modifying whatever already exists as necessary. * @param string $table The table name. From 2f00884e792f3c9a8fb4c2ea992b516fcd8e08a9 Mon Sep 17 00:00:00 2001 From: Hamish Friedlander Date: Wed, 29 Aug 2012 14:30:10 +1200 Subject: [PATCH 2/2] FIX If ClassName read from DB doesnt exist, dont break We know the subclass of a record by its ClassName value, but code changes might have meant that class no longer exists. We used to just break, but this patch overrides the apparent value of ClassName to be one that exists in that situation --- core/ClassInfo.php | 6 ++++-- model/DataObject.php | 47 ++++++++++++++++++++++++++++++++++++-------- model/Versioned.php | 2 +- 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/core/ClassInfo.php b/core/ClassInfo.php index 7aa17fa56..9c0f845fc 100644 --- a/core/ClassInfo.php +++ b/core/ClassInfo.php @@ -58,8 +58,10 @@ class ClassInfo { * Returns the manifest of all classes which are present in the database. * @param string $class Class name to check enum values for ClassName field */ - static function getValidSubClasses($class = 'SiteTree') { - return DB::getConn()->enumValuesForField($class, 'ClassName'); + static function getValidSubClasses($class = 'SiteTree', $includeUnbacked = false) { + $classes = DB::getConn()->enumValuesForField($class, 'ClassName'); + if (!$includeUnbacked) $classes = array_filter($classes, array('ClassInfo', 'exists')); + return $classes; } /** diff --git a/model/DataObject.php b/model/DataObject.php index d7f5db288..d9320ae3e 100644 --- a/model/DataObject.php +++ b/model/DataObject.php @@ -181,9 +181,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity */ public static function database_fields($class) { if(get_parent_class($class) == 'DataObject') { + $db = DB::getConn(); + $existing = $db->hasField($class, 'ClassName') ? $db->query("SELECT DISTINCT \"ClassName\" FROM \"$class\"")->column() : array(); + return array_merge ( array ( - 'ClassName' => "Enum('" . implode(', ', ClassInfo::subclassesFor($class)) . "')", + 'ClassName' => "Enum('" . implode(', ', array_unique(array_merge($existing, ClassInfo::subclassesFor($class)))) . "')", 'Created' => 'SS_Datetime', 'LastEdited' => 'SS_Datetime' ), @@ -443,6 +446,17 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity } } } + + function getObsoleteClassName() { + $className = $this->getField("ClassName"); + if (!ClassInfo::exists($className)) return $className; + } + + function getClassName() { + $className = $this->getField("ClassName"); + if (!ClassInfo::exists($className)) return get_class($this); + return $className; + } /** * Set the ClassName attribute. {@link $class} is also updated. @@ -985,17 +999,34 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity $firstWrite = false; $this->brokenOnWrite = true; $isNewRecord = false; - - if(self::get_validation_enabled()) { + + $writeException = null; + + if ($this->ObsoleteClassName) { + $writeException = new ValidationException( + "Object is of class '{$this->ObsoleteClassName}' which doesn't exist - ". + "you need to change the ClassName before you can write it", + E_USER_WARNING + ); + } + else if(self::get_validation_enabled()) { $valid = $this->validate(); - if(!$valid->valid()) { - // Used by DODs to clean up after themselves, eg, Versioned - $this->extend('onAfterSkippedWrite'); - throw new ValidationException($valid, "Validation error writing a $this->class object: " . $valid->message() . ". Object not written.", E_USER_WARNING); - return false; + if (!$valid->valid()) { + $writeException = new ValidationException( + $valid, + "Validation error writing a $this->class object: " . $valid->message() . ". Object not written.", + E_USER_WARNING + ); } } + if($writeException) { + // Used by DODs to clean up after themselves, eg, Versioned + $this->extend('onAfterSkippedWrite'); + throw $writeException; + return false; + } + $this->onBeforeWrite(); if($this->brokenOnWrite) { user_error("$this->class has a broken onBeforeWrite() function. Make sure that you call parent::onBeforeWrite().", E_USER_ERROR); diff --git a/model/Versioned.php b/model/Versioned.php index 25da1a7c4..b8efd4d87 100644 --- a/model/Versioned.php +++ b/model/Versioned.php @@ -1103,7 +1103,7 @@ class Versioned_Version extends ViewableData { $record['ID'] = $record['RecordID']; $className = $record['ClassName']; - $this->object = new $className($record); + $this->object = ClassInfo::exists($className) ? new $className($record) : new DataObject($record); $this->failover = $this->object; parent::__construct();