/src/ORM/FieldType/DBClassName.php

https://gitlab.com/djpmedia/silverstripe-framework · PHP · 221 lines · 116 code · 26 blank · 79 comment · 15 complexity · 2aeeaa584ef59de5e4c160e036769cfe MD5 · raw file

  1. <?php
  2. namespace SilverStripe\ORM\FieldType;
  3. use SilverStripe\Core\ClassInfo;
  4. use SilverStripe\Core\Config\Config;
  5. use SilverStripe\ORM\DataObject;
  6. use SilverStripe\ORM\DB;
  7. /**
  8. * Represents a classname selector, which respects obsolete clasess.
  9. */
  10. class DBClassName extends DBEnum
  11. {
  12. /**
  13. * Base classname of class to enumerate.
  14. * If 'DataObject' then all classes are included.
  15. * If empty, then the baseClass of the parent object will be used
  16. *
  17. * @var string|null
  18. */
  19. protected $baseClass = null;
  20. /**
  21. * Parent object
  22. *
  23. * @var DataObject|null
  24. */
  25. protected $record = null;
  26. /**
  27. * Classname spec cache for obsolete classes. The top level keys are the table, each of which contains
  28. * nested arrays with keys mapped to field names. The values of the lowest level array are the classnames
  29. *
  30. * @var array
  31. */
  32. protected static $classname_cache = array();
  33. private static $index = true;
  34. /**
  35. * Clear all cached classname specs. It's necessary to clear all cached subclassed names
  36. * for any classes if a new class manifest is generated.
  37. */
  38. public static function clear_classname_cache()
  39. {
  40. self::$classname_cache = array();
  41. }
  42. /**
  43. * Create a new DBClassName field
  44. *
  45. * @param string $name Name of field
  46. * @param string|null $baseClass Optional base class to limit selections
  47. * @param array $options Optional parameters for this DBField instance
  48. */
  49. public function __construct($name = null, $baseClass = null, $options = [])
  50. {
  51. $this->setBaseClass($baseClass);
  52. parent::__construct($name, null, null, $options);
  53. }
  54. /**
  55. * @return void
  56. */
  57. public function requireField()
  58. {
  59. $parts = array(
  60. 'datatype' => 'enum',
  61. 'enums' => $this->getEnumObsolete(),
  62. 'character set' => 'utf8',
  63. 'collate' => 'utf8_general_ci',
  64. 'default' => $this->getDefault(),
  65. 'table' => $this->getTable(),
  66. 'arrayValue' => $this->arrayValue
  67. );
  68. $values = array(
  69. 'type' => 'enum',
  70. 'parts' => $parts
  71. );
  72. DB::require_field($this->getTable(), $this->getName(), $values);
  73. }
  74. /**
  75. * Get the base dataclass for the list of subclasses
  76. *
  77. * @return string
  78. */
  79. public function getBaseClass()
  80. {
  81. // Use explicit base class
  82. if ($this->baseClass) {
  83. return $this->baseClass;
  84. }
  85. // Default to the basename of the record
  86. $schema = DataObject::getSchema();
  87. if ($this->record) {
  88. return $schema->baseDataClass($this->record);
  89. }
  90. // During dev/build only the table is assigned
  91. $tableClass = $schema->tableClass($this->getTable());
  92. if ($tableClass && ($baseClass = $schema->baseDataClass($tableClass))) {
  93. return $baseClass;
  94. }
  95. // Fallback to global default
  96. return DataObject::class;
  97. }
  98. /**
  99. * Get the base name of the current class
  100. * Useful as a non-fully qualified CSS Class name in templates.
  101. *
  102. * @return string|null
  103. */
  104. public function getShortName()
  105. {
  106. $value = $this->getValue();
  107. if (empty($value) || !ClassInfo::exists($value)) {
  108. return null;
  109. }
  110. return ClassInfo::shortName($value);
  111. }
  112. /**
  113. * Assign the base class
  114. *
  115. * @param string $baseClass
  116. * @return $this
  117. */
  118. public function setBaseClass($baseClass)
  119. {
  120. $this->baseClass = $baseClass;
  121. return $this;
  122. }
  123. /**
  124. * Get list of classnames that should be selectable
  125. *
  126. * @return array
  127. */
  128. public function getEnum()
  129. {
  130. $classNames = ClassInfo::subclassesFor($this->getBaseClass());
  131. $dataobject = strtolower(DataObject::class);
  132. unset($classNames[$dataobject]);
  133. return array_values($classNames);
  134. }
  135. /**
  136. * Get the list of classnames, including obsolete classes.
  137. *
  138. * If table or name are not set, or if it is not a valid field on the given table,
  139. * then only known classnames are returned.
  140. *
  141. * Values cached in this method can be cleared via `DBClassName::clear_classname_cache();`
  142. *
  143. * @return array
  144. */
  145. public function getEnumObsolete()
  146. {
  147. // Without a table or field specified, we can only retrieve known classes
  148. $table = $this->getTable();
  149. $name = $this->getName();
  150. if (empty($table) || empty($name)) {
  151. return $this->getEnum();
  152. }
  153. // Ensure the table level cache exists
  154. if (empty(self::$classname_cache[$table])) {
  155. self::$classname_cache[$table] = array();
  156. }
  157. // Check existing cache
  158. if (!empty(self::$classname_cache[$table][$name])) {
  159. return self::$classname_cache[$table][$name];
  160. }
  161. // Get all class names
  162. $classNames = $this->getEnum();
  163. if (DB::get_schema()->hasField($table, $name)) {
  164. $existing = DB::query("SELECT DISTINCT \"{$name}\" FROM \"{$table}\"")->column();
  165. $classNames = array_unique(array_merge($classNames, $existing));
  166. }
  167. // Cache and return
  168. self::$classname_cache[$table][$name] = $classNames;
  169. return $classNames;
  170. }
  171. public function setValue($value, $record = null, $markChanged = true)
  172. {
  173. parent::setValue($value, $record, $markChanged);
  174. if ($record instanceof DataObject) {
  175. $this->record = $record;
  176. }
  177. }
  178. public function getDefault()
  179. {
  180. // Check for assigned default
  181. $default = parent::getDefault();
  182. if ($default) {
  183. return $default;
  184. }
  185. // Allow classes to set default class
  186. $baseClass = $this->getBaseClass();
  187. $defaultClass = Config::inst()->get($baseClass, 'default_classname');
  188. if ($defaultClass && class_exists($defaultClass)) {
  189. return $defaultClass;
  190. }
  191. // Fallback to first option
  192. $enum = $this->getEnum();
  193. return reset($enum);
  194. }
  195. }