/** * Normalizes the field name to table mapping. * * @return string */ public function getDbName() { // Special handler for "NULL" relations if ($this->name == "NULL") { return $this->name; } // This code finds the table where the field named $this->name lives // Todo: move to somewhere more appropriate, such as DataMapper, the // magical class-to-be? $candidateClass = $this->model; while ($candidateClass != 'DataObject') { if (DataObject::has_own_table($candidateClass) && singleton($candidateClass)->hasOwnTableDatabaseField($this->name)) { break; } $candidateClass = get_parent_class($candidateClass); } if ($candidateClass == 'DataObject') { // fallback to the provided name in the event of a joined column // name (as the candidate class doesn't check joined records) $parts = explode('.', $this->fullName); return '"' . implode('"."', $parts) . '"'; } //Cannot use ClassName.PropertyName in OrientDB queries return "{$this->name}"; }
/** * @see DataExtension::augmentSQL() */ public function augmentSQL(SQLQuery &$query) { $select = $query->getSelect(); if (empty($select) || $query->getDelete() || in_array("COUNT(*)", $select) || in_array("count(*)", $select)) { return; } if (!isset(self::$sortTables[$this->owner->class])) { $classes = array_reverse(ClassInfo::dataClassesFor($this->owner->class)); $class = null; foreach ($classes as $cls) { if (DataObject::has_own_table($cls) && ($fields = DataObject::database_fields($cls)) && isset($fields['SortOrder'])) { $class = $cls; break; } } self::$sortTables[$this->owner->class] = $class; } else { $class = self::$sortTables[$this->owner->class]; } if ($class) { $query->addOrderBy("\"{$class}\".\"SortOrder\" " . self::$sort_dir); } else { $query->addOrderBy("\"SortOrder\" " . self::$sort_dir); } }
/** * Get all database fields to translate * * @param string $class Class name * @return array List of translated fields */ public function getTranslatedFields($class) { if (isset($this->translatedFields[$class])) { return $this->translatedFields[$class]; } $fields = array(); $hierarchy = ClassInfo::ancestry($class); foreach ($hierarchy as $class) { // Skip classes without tables if (!DataObject::has_own_table($class)) { continue; } // Check translated fields for this class $translatedFields = FluentExtension::translated_fields_for($class); if (empty($translatedFields)) { continue; } // Save fields $fields = array_merge($fields, array_keys($translatedFields)); } $this->translatedFields[$class] = $fields; return $fields; }
/** * Get all the classes involved in a DataObject hierarchy - both super and optionally subclasses * * @static * @param String $class - The class to query * @param bool $includeSubclasses - True to return subclasses as well as super classes * @param bool $dataOnly - True to only return classes that have tables * @return Array - Integer keys, String values as classes sorted by depth (most super first) */ static function hierarchy($class, $includeSubclasses = true, $dataOnly = false) { $key = "{$class}!" . ($includeSubclasses ? 'sc' : 'an') . '!' . ($dataOnly ? 'do' : 'al'); if (!isset(self::$hierarchy[$key])) { $classes = array_values(ClassInfo::ancestry($class)); if ($includeSubclasses) { $classes = array_unique(array_merge($classes, array_values(ClassInfo::subclassesFor($class)))); } $idx = array_search('DataObject', $classes); if ($idx !== false) { array_splice($classes, 0, $idx + 1); } if ($dataOnly) { foreach ($classes as $i => $class) { if (!DataObject::has_own_table($class)) { unset($classes[$i]); } } } self::$hierarchy[$key] = $classes; } return self::$hierarchy[$key]; }
/** * Get all database tables in the class ancestry and their respective * translatable fields * * @return array */ protected function getTranslatedTables() { $includedTables = array(); foreach ($this->owner->getClassAncestry() as $class) { // Skip classes without tables if (!DataObject::has_own_table($class)) { continue; } // Check translated fields for this class $translatedFields = self::translated_fields_for($class); if (empty($translatedFields)) { continue; } // Mark this table as translatable $includedTables[$class] = array_keys($translatedFields); } return $includedTables; }
/** * Normalizes the field name to table mapping. * * @return string */ public function getDbName() { // Special handler for "NULL" relations if ($this->name == "NULL") { return $this->name; } // SRM: This code finds the table where the field named $this->name lives // Todo: move to somewhere more appropriate, such as DataMapper, the magical class-to-be? $candidateClass = $this->model; while ($candidateClass != 'DataObject') { if (DataObject::has_own_table($candidateClass) && singleton($candidateClass)->hasOwnTableDatabaseField($this->name)) { break; } $candidateClass = get_parent_class($candidateClass); } if ($candidateClass == 'DataObject') { user_error("Couldn't find field {$this->name} in any of {$this->model}'s tables.", E_USER_ERROR); } return "\"{$candidateClass}\".\"{$this->name}\""; }
/** * Determine if a table is supporting the Versioned extensions (e.g. $table_versions does exists) * * @param string $table Table name * @return boolean */ function canBeVersioned($table) { return ClassInfo::exists($table) && ClassInfo::is_subclass_of($table, 'DataObject') && DataObject::has_own_table($table); }
/** * Returns the table name in the class hierarchy which contains a given * field column for a {@link DataObject}. If the field does not exist, this * will return null. * * @param string $candidateClass * @param string $fieldName * * @return string */ public static function table_for_object_field($candidateClass, $fieldName) { if (!$candidateClass || !$fieldName || !class_exists($candidateClass) || !is_subclass_of($candidateClass, 'DataObject')) { return null; } //normalise class name $candidateClass = self::class_name($candidateClass); $exists = self::exists($candidateClass); // Short circuit for fixed fields $fixed = DataObject::config()->fixed_fields; if ($exists && isset($fixed[$fieldName])) { return self::baseDataClass($candidateClass); } // Find regular field while ($candidateClass && $candidateClass != 'DataObject' && $exists) { if (DataObject::has_own_table($candidateClass) && DataObject::has_own_table_database_field($candidateClass, $fieldName)) { break; } $candidateClass = get_parent_class($candidateClass); $exists = $candidateClass && self::exists($candidateClass); } if (!$candidateClass || !$exists) { return null; } return $candidateClass; }
/** * Construct a new cleaner for the given DataObject class. * * @param string $class */ public function __construct($class) { set_time_limit(self::get_time_limit()); $this->className = $class; $this->dataObject = singleton($this->className); $this->baseClassName = ClassInfo::baseDataClass($this->className); $this->tableAncestors = ClassInfo::ancestry($this->className, true); $this->allAncestors = ClassInfo::ancestry($this->className, false); // Remove Object, ViewableData, DataObject $this->allAncestors = array_slice($this->allAncestors, 3, count($this->allAncestors) - 3, true); JanitorDebug::message("Class: {$this->className}", 'h3'); JanitorDebug::message('Table ancestors: ' . var_export($this->tableAncestors, true), 'pre'); JanitorDebug::message('All ancestors: ' . var_export($this->allAncestors, true), 'pre'); // Select the table that we will be working with. $this->table = array_reverse($this->tableAncestors, false); $this->table = DataObject::has_own_table($this->className)? $this->className: array_shift($this->table); JanitorDebug::message("Selected table: {$this->table}", 'h4'); }
/** * Add a DataObject subclass whose instances should be included in this index * * Can only be called when addFulltextField, addFilterField, addSortField and addAllFulltextFields have not * yet been called for this index instance * * @throws Exception * @param String $class - The class to include * @param array $options - TODO: Remove */ public function addClass($class, $options = array()) { if ($this->fulltextFields || $this->filterFields || $this->sortFields) { throw new Exception('Can\'t add class to Index after fields have already been added'); } if (!DataObject::has_own_table($class)) { throw new InvalidArgumentException('Can\'t add classes which don\'t have data tables (no $db or $has_one set on the class)'); } $options = array_merge(array('include_children' => true), $options); $this->classes[$class] = $options; }
/** * Rebuild all vfi fields. */ public function rebuildVFI($field = '') { if ($field) { $this->isRebuilding = true; $spec = $this->getVFISpec($field); $fn = $this->getVFIFieldName($field); $val = $this->getVFI($field, true); if ($spec['Type'] == self::TYPE_LIST) { if (is_object($val)) { $val = $val->toArray(); } // this would be an ArrayList or DataList if (!is_array($val)) { $val = array($val); } // this would be a scalar value $val = self::encode_list($val); } else { if (is_array($val)) { $val = (string) $val[0]; } // if they give us an array, just take the first value if (is_object($val)) { $val = (string) $val->first(); } // if a SS_List, take the first as well } if (Config::inst()->get('VirtualFieldIndex', 'fast_writes_enabled')) { // NOTE: this is usually going to be bad practice, but if you // have a lot of products and a lot of on...Write handlers that // can get tedious really quick. This is just here as an option. $table = ''; foreach ($this->owner->getClassAncestry() as $ancestor) { if (DataObject::has_own_table($ancestor)) { $sing = singleton($ancestor); if ($sing->hasOwnTableDatabaseField($fn)) { $table = $ancestor; break; } } } if (!empty($table)) { DB::query($sql = sprintf("UPDATE %s SET %s = '%s' WHERE ID = '%d'", $table, $fn, Convert::raw2sql($val), $this->owner->ID)); DB::query(sprintf("UPDATE %s_Live SET %s = '%s' WHERE ID = '%d'", $table, $fn, Convert::raw2sql($val), $this->owner->ID)); $this->owner->setField($fn, $val); } else { // if we couldn't figure out the right table, fall back to the old fashioned way $this->owner->setField($fn, $val); $this->owner->write(); } } else { $this->owner->setField($fn, $val); $this->owner->write(); } $this->isRebuilding = false; } else { // rebuild all fields if they didn't specify foreach ($this->getVFISpec() as $field => $spec) { $this->rebuildVFI($field); } } }
/** * @todo Improve documentation */ static function ancestry($class, $onlyWithTables = false) { global $_ALL_CLASSES; if (is_object($class)) { $class = $class->class; } else { if (!is_string($class)) { user_error("Bad class value " . var_export($class, true) . " passed to ClassInfo::ancestry()", E_USER_WARNING); } } $items = $_ALL_CLASSES['parents'][$class]; $items[$class] = $class; if ($onlyWithTables) { foreach ($items as $item) { if (!DataObject::has_own_table($item)) { unset($items[$item]); } } } return $items; }
/** * Remove invalid records from tables - that is, records that don't have * corresponding records in their parent class tables. */ public function cleanup() { $allClasses = get_declared_classes(); foreach ($allClasses as $class) { if (get_parent_class($class) == 'DataObject') { $baseClasses[] = $class; } } foreach ($baseClasses as $baseClass) { // Get data classes $subclasses = ClassInfo::subclassesFor($baseClass); unset($subclasses[0]); foreach ($subclasses as $k => $subclass) { if (DataObject::has_own_table($subclass)) { unset($subclasses[$k]); } } if ($subclasses) { $records = DB::query("SELECT * FROM \"{$baseClass}\""); foreach ($subclasses as $subclass) { $recordExists[$subclass] = DB::query("SELECT \"ID\" FROM \"{$subclass}\"")->keyedColumn(); } foreach ($records as $record) { foreach ($subclasses as $subclass) { $id = $record['ID']; if ($record['ClassName'] != $subclass && !is_subclass_of($record['ClassName'], $subclass) && isset($recordExists[$subclass][$id])) { $sql = "DELETE FROM \"{$subclass}\" WHERE \"ID\" = {$record['ID']}"; echo "<li>{$sql}"; DB::query($sql); } } } } } }
/** * Returns the passed class name along with all its parent class names in an * array, sorted with the root class first. * * @param string $class * @param bool $tablesOnly Only return classes that have a table in the db. * @return array */ public static function ancestry($class, $tablesOnly = false) { $ancestry = array(); if (is_object($class)) { $class = get_class($class); } elseif (!is_string($class)) { throw new Exception(sprintf('Invalid class value %s, must be an object or string', var_export($class, true))); } do { if (!$tablesOnly || DataObject::has_own_table($class)) { $ancestry[$class] = $class; } } while ($class = get_parent_class($class)); return array_reverse($ancestry); }
/** * Returns the table name in the class hierarchy which contains a given * field column for a {@link DataObject}. If the field does not exist, this * will return null. * * @param string $candidateClass * @param string $fieldName * * @return string */ public static function table_for_object_field($candidateClass, $fieldName) { if (!$candidateClass || !$fieldName || !is_subclass_of($candidateClass, 'DataObject')) { return null; } //normalise class name $candidateClass = self::class_name($candidateClass); $exists = self::exists($candidateClass); while ($candidateClass && $candidateClass != 'DataObject' && $exists) { if (DataObject::has_own_table($candidateClass)) { $inst = singleton($candidateClass); if ($inst->hasOwnTableDatabaseField($fieldName)) { break; } } $candidateClass = get_parent_class($candidateClass); $exists = $candidateClass && self::exists($candidateClass); } if (!$candidateClass || !$exists) { return null; } return $candidateClass; }
/** * @param $className * @param $ids * @param string $postfix */ private function deleteTablePostfix($className, $ids, $postfix = '') { if (empty($ids)) { return; } if ($postfix === 'Stage') { $postfix = ''; } $singleton = singleton($className); $ancestry = array_reverse(array_filter($singleton->getClassAncestry(), function ($class) { return DataObject::has_own_table($class); })); $field = DBField::create_field('Int', null, 'ID'); $ids = '(' . implode(', ', array_map(function ($id) use($field) { $id = $id instanceof \DataObject ? $id->ID : $id; return $field->prepValueForDB($id); }, $ids)) . ')'; foreach ($ancestry as $class) { $table = $class . ($postfix ? '_' . $postfix : ''); $sql = "DELETE FROM `{$table}` WHERE ID IN {$ids}"; DB::query($sql); } }
/** * Returns the table name in the class hierarchy which contains a given * field column for a {@link DataObject}. If the field does not exist, this * will return null. * * @param string $candidateClass * @param string $fieldName * * @return string */ public static function table_for_object_field($candidateClass, $fieldName) { if (!$candidateClass || !$fieldName) { return null; } $exists = class_exists($candidateClass); while ($candidateClass && $candidateClass != 'DataObject' && $exists) { if (DataObject::has_own_table($candidateClass)) { $inst = singleton($candidateClass); if ($inst->hasOwnTableDatabaseField($fieldName)) { break; } } $candidateClass = get_parent_class($candidateClass); $exists = class_exists($candidateClass); } if (!$candidateClass || !$exists) { return null; } return $candidateClass; }
public function TestHasOwnTable() { /* Test DataObject::has_own_table() returns true if the object has $has_one or $db values */ $this->assertTrue(DataObject::has_own_table("DataObjectTest_Player")); $this->assertTrue(DataObject::has_own_table("DataObjectTest_Team")); $this->assertTrue(DataObject::has_own_table("DataObjectTest_Fixture")); /* Root DataObject that always have a table, even if they lack both $db and $has_one */ $this->assertTrue(DataObject::has_own_table("DataObjectTest_FieldlessTable")); /* Subclasses without $db or $has_one don't have a table */ $this->assertFalse(DataObject::has_own_table("DataObjectTest_FieldlessSubTable")); /* Return false if you don't pass it a subclass of DataObject */ $this->assertFalse(DataObject::has_own_table("DataObject")); $this->assertFalse(DataObject::has_own_table("ViewableData")); $this->assertFalse(DataObject::has_own_table("ThisIsntADataObject")); }
/** * Returns the passed class name along with all its parent class names in an * array, sorted with the root class first. * * @param string $class * @param bool $tablesOnly Only return classes that have a table in the db. * @return array */ public static function ancestry($class, $tablesOnly = false) { if (!is_string($class)) { $class = get_class($class); } $cacheKey = $class . '_' . (string) $tablesOnly; $parent = $class; if (!isset(self::$_cache_ancestry[$cacheKey])) { $ancestry = array(); do { if (!$tablesOnly || DataObject::has_own_table($parent)) { $ancestry[$parent] = $parent; } } while ($parent = get_parent_class($parent)); self::$_cache_ancestry[$cacheKey] = array_reverse($ancestry); } return self::$_cache_ancestry[$cacheKey]; }