Complex classes like DataObjectSchema often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use DataObjectSchema, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 18 | class DataObjectSchema |
||
| 19 | { |
||
| 20 | use Injectable; |
||
| 21 | use Configurable; |
||
| 22 | |||
| 23 | /** |
||
| 24 | * Default separate for table namespaces. Can be set to any string for |
||
| 25 | * databases that do not support some characters. |
||
| 26 | * |
||
| 27 | * @config |
||
| 28 | * @var string |
||
| 29 | */ |
||
| 30 | private static $table_namespace_separator = '_'; |
||
| 31 | |||
| 32 | /** |
||
| 33 | * Cache of database fields |
||
| 34 | * |
||
| 35 | * @var array |
||
| 36 | */ |
||
| 37 | protected $databaseFields = []; |
||
| 38 | |||
| 39 | /** |
||
| 40 | * Cache of database indexes |
||
| 41 | * |
||
| 42 | * @var array |
||
| 43 | */ |
||
| 44 | protected $databaseIndexes = []; |
||
| 45 | |||
| 46 | /** |
||
| 47 | * Cache of composite database field |
||
| 48 | * |
||
| 49 | * @var array |
||
| 50 | */ |
||
| 51 | protected $compositeFields = []; |
||
| 52 | |||
| 53 | /** |
||
| 54 | * Cache of table names |
||
| 55 | * |
||
| 56 | * @var array |
||
| 57 | */ |
||
| 58 | protected $tableNames = []; |
||
| 59 | |||
| 60 | /** |
||
| 61 | * Clear cached table names |
||
| 62 | */ |
||
| 63 | public function reset() |
||
| 70 | |||
| 71 | /** |
||
| 72 | * Get all table names |
||
| 73 | * |
||
| 74 | * @return array |
||
| 75 | */ |
||
| 76 | public function getTableNames() |
||
| 81 | |||
| 82 | /** |
||
| 83 | * Given a DataObject class and a field on that class, determine the appropriate SQL for |
||
| 84 | * selecting / filtering on in a SQL string. Note that $class must be a valid class, not an |
||
| 85 | * arbitrary table. |
||
| 86 | * |
||
| 87 | * The result will be a standard ANSI-sql quoted string in "Table"."Column" format. |
||
| 88 | * |
||
| 89 | * @param string $class Class name (not a table). |
||
| 90 | * @param string $field Name of field that belongs to this class (or a parent class) |
||
| 91 | * @return string The SQL identifier string for the corresponding column for this field |
||
| 92 | */ |
||
| 93 | public function sqlColumnForField($class, $field) |
||
| 101 | |||
| 102 | /** |
||
| 103 | * Get table name for the given class. |
||
| 104 | * |
||
| 105 | * Note that this does not confirm a table actually exists (or should exist), but returns |
||
| 106 | * the name that would be used if this table did exist. |
||
| 107 | * |
||
| 108 | * @param string $class |
||
| 109 | * @return string Returns the table name, or null if there is no table |
||
| 110 | */ |
||
| 111 | public function tableName($class) |
||
| 120 | /** |
||
| 121 | * Returns the root class (the first to extend from DataObject) for the |
||
| 122 | * passed class. |
||
| 123 | * |
||
| 124 | * @param string|object $class |
||
| 125 | * @return string |
||
| 126 | * @throws InvalidArgumentException |
||
| 127 | */ |
||
| 128 | public function baseDataClass($class) |
||
| 140 | |||
| 141 | /** |
||
| 142 | * Get the base table |
||
| 143 | * |
||
| 144 | * @param string|object $class |
||
| 145 | * @return string |
||
| 146 | */ |
||
| 147 | public function baseDataTable($class) |
||
| 151 | |||
| 152 | /** |
||
| 153 | * fieldSpec should exclude virtual fields (such as composite fields), and only include fields with a db column. |
||
| 154 | */ |
||
| 155 | const DB_ONLY = 1; |
||
| 156 | |||
| 157 | /** |
||
| 158 | * fieldSpec should only return fields that belong to this table, and not any ancestors |
||
| 159 | */ |
||
| 160 | const UNINHERITED = 2; |
||
| 161 | |||
| 162 | /** |
||
| 163 | * fieldSpec should prefix all field specifications with the class name in RecordClass.Column(spec) format. |
||
| 164 | */ |
||
| 165 | const INCLUDE_CLASS = 4; |
||
| 166 | |||
| 167 | /** |
||
| 168 | * Get all DB field specifications for a class, including ancestors and composite fields. |
||
| 169 | * |
||
| 170 | * @param string|DataObject $classOrInstance |
||
| 171 | * @param int $options Bitmask of options |
||
| 172 | * - UNINHERITED Limit to only this table |
||
| 173 | * - DB_ONLY Exclude virtual fields (such as composite fields), and only include fields with a db column. |
||
| 174 | * - INCLUDE_CLASS Prefix the field specification with the class name in RecordClass.Column(spec) format. |
||
| 175 | * @return array List of fields, where the key is the field name and the value is the field specification. |
||
| 176 | */ |
||
| 177 | public function fieldSpecs($classOrInstance, $options = 0) |
||
| 210 | |||
| 211 | |||
| 212 | /** |
||
| 213 | * Get specifications for a single class field |
||
| 214 | * |
||
| 215 | * @param string|DataObject $classOrInstance Name or instance of class |
||
| 216 | * @param string $fieldName Name of field to retrieve |
||
| 217 | * @param int $options Bitmask of options |
||
| 218 | * - UNINHERITED Limit to only this table |
||
| 219 | * - DB_ONLY Exclude virtual fields (such as composite fields), and only include fields with a db column. |
||
| 220 | * - INCLUDE_CLASS Prefix the field specification with the class name in RecordClass.Column(spec) format. |
||
| 221 | * @return string|null Field will be a string in FieldClass(args) format, or |
||
| 222 | * RecordClass.FieldClass(args) format if using INCLUDE_CLASS. Will be null if no field is found. |
||
| 223 | */ |
||
| 224 | public function fieldSpec($classOrInstance, $fieldName, $options = 0) |
||
| 229 | |||
| 230 | /** |
||
| 231 | * Find the class for the given table |
||
| 232 | * |
||
| 233 | * @param string $table |
||
| 234 | * @return string|null The FQN of the class, or null if not found |
||
| 235 | */ |
||
| 236 | public function tableClass($table) |
||
| 255 | |||
| 256 | /** |
||
| 257 | * Cache all table names if necessary |
||
| 258 | */ |
||
| 259 | protected function cacheTableNames() |
||
| 281 | |||
| 282 | /** |
||
| 283 | * Generate table name for a class. |
||
| 284 | * |
||
| 285 | * Note: some DB schema have a hard limit on table name length. This is not enforced by this method. |
||
| 286 | * See dev/build errors for details in case of table name violation. |
||
| 287 | * |
||
| 288 | * @param string $class |
||
| 289 | * @return string |
||
| 290 | */ |
||
| 291 | protected function buildTableName($class) |
||
| 303 | |||
| 304 | /** |
||
| 305 | * Return the complete map of fields to specification on this object, including fixed_fields. |
||
| 306 | * "ID" will be included on every table. |
||
| 307 | * |
||
| 308 | * @param string $class Class name to query from |
||
| 309 | * @param bool $aggregated Include fields in entire hierarchy, rather than just on this table |
||
| 310 | * @return array Map of fieldname to specification, similiar to {@link DataObject::$db}. |
||
| 311 | */ |
||
| 312 | public function databaseFields($class, $aggregated = true) |
||
| 329 | |||
| 330 | /** |
||
| 331 | * Gets a single database field. |
||
| 332 | * |
||
| 333 | * @param string $class Class name to query from |
||
| 334 | * @param string $field Field name |
||
| 335 | * @param bool $aggregated Include fields in entire hierarchy, rather than just on this table |
||
| 336 | * @return string|null Field specification, or null if not a field |
||
| 337 | */ |
||
| 338 | public function databaseField($class, $field, $aggregated = true) |
||
| 343 | |||
| 344 | /** |
||
| 345 | * @param string $class |
||
| 346 | * @param bool $aggregated |
||
| 347 | * |
||
| 348 | * @return array |
||
| 349 | */ |
||
| 350 | public function databaseIndexes($class, $aggregated = true) |
||
| 363 | |||
| 364 | /** |
||
| 365 | * Check if the given class has a table |
||
| 366 | * |
||
| 367 | * @param string $class |
||
| 368 | * @return bool |
||
| 369 | */ |
||
| 370 | public function classHasTable($class) |
||
| 375 | |||
| 376 | /** |
||
| 377 | * Returns a list of all the composite if the given db field on the class is a composite field. |
||
| 378 | * Will check all applicable ancestor classes and aggregate results. |
||
| 379 | * |
||
| 380 | * Can be called directly on an object. E.g. Member::composite_fields(), or Member::composite_fields(null, true) |
||
| 381 | * to aggregate. |
||
| 382 | * |
||
| 383 | * Includes composite has_one (Polymorphic) fields |
||
| 384 | * |
||
| 385 | * @param string $class Name of class to check |
||
| 386 | * @param bool $aggregated Include fields in entire hierarchy, rather than just on this table |
||
| 387 | * @return array List of composite fields and their class spec |
||
| 388 | */ |
||
| 389 | public function compositeFields($class, $aggregated = true) |
||
| 407 | |||
| 408 | /** |
||
| 409 | * Get a composite field for a class |
||
| 410 | * |
||
| 411 | * @param string $class Class name to query from |
||
| 412 | * @param string $field Field name |
||
| 413 | * @param bool $aggregated Include fields in entire hierarchy, rather than just on this table |
||
| 414 | * @return string|null Field specification, or null if not a field |
||
| 415 | */ |
||
| 416 | public function compositeField($class, $field, $aggregated = true) |
||
| 421 | |||
| 422 | /** |
||
| 423 | * Cache all database and composite fields for the given class. |
||
| 424 | * Will do nothing if already cached |
||
| 425 | * |
||
| 426 | * @param string $class Class name to cache |
||
| 427 | */ |
||
| 428 | protected function cacheDatabaseFields($class) |
||
| 486 | |||
| 487 | /** |
||
| 488 | * Cache all indexes for the given class. |
||
| 489 | * Will do nothing if already cached |
||
| 490 | * |
||
| 491 | * @param $class |
||
| 492 | */ |
||
| 493 | protected function cacheDatabaseIndexes($class) |
||
| 550 | |||
| 551 | /** |
||
| 552 | * Returns the table name in the class hierarchy which contains a given |
||
| 553 | * field column for a {@link DataObject}. If the field does not exist, this |
||
| 554 | * will return null. |
||
| 555 | * |
||
| 556 | * @param string $candidateClass |
||
| 557 | * @param string $fieldName |
||
| 558 | * @return string |
||
| 559 | */ |
||
| 560 | public function tableForField($candidateClass, $fieldName) |
||
| 568 | |||
| 569 | /** |
||
| 570 | * Returns the class name in the class hierarchy which contains a given |
||
| 571 | * field column for a {@link DataObject}. If the field does not exist, this |
||
| 572 | * will return null. |
||
| 573 | * |
||
| 574 | * @param string $candidateClass |
||
| 575 | * @param string $fieldName |
||
| 576 | * @return string |
||
| 577 | */ |
||
| 578 | public function classForField($candidateClass, $fieldName) |
||
| 602 | |||
| 603 | /** |
||
| 604 | * Return information about a specific many_many component. Returns a numeric array. |
||
| 605 | * The first item in the array will be the class name of the relation. |
||
| 606 | * |
||
| 607 | * Standard many_many return type is: |
||
| 608 | * |
||
| 609 | * array( |
||
| 610 | * <manyManyClass>, Name of class for relation. E.g. "Categories" |
||
| 611 | * <classname>, The class that relation is defined in e.g. "Product" |
||
| 612 | * <candidateName>, The target class of the relation e.g. "Category" |
||
| 613 | * <parentField>, The field name pointing to <classname>'s table e.g. "ProductID". |
||
| 614 | * <childField>, The field name pointing to <candidatename>'s table e.g. "CategoryID". |
||
| 615 | * <joinTableOrRelation> The join table between the two classes e.g. "Product_Categories". |
||
| 616 | * If the class name is 'ManyManyThroughList' then this is the name of the |
||
| 617 | * has_many relation. |
||
| 618 | * ) |
||
| 619 | * @param string $class Name of class to get component for |
||
| 620 | * @param string $component The component name |
||
| 621 | * @return array|null |
||
| 622 | */ |
||
| 623 | public function manyManyComponent($class, $component) |
||
| 659 | |||
| 660 | |||
| 661 | |||
| 662 | /** |
||
| 663 | * Parse a belongs_many_many component to extract class and relationship name |
||
| 664 | * |
||
| 665 | * @param string $parentClass Name of class |
||
| 666 | * @param string $component Name of relation on class |
||
| 667 | * @param string $specification specification for this belongs_many_many |
||
| 668 | * @return array Array with child class and relation name |
||
| 669 | */ |
||
| 670 | protected function parseBelongsManyManyComponent($parentClass, $component, $specification) |
||
| 705 | |||
| 706 | /** |
||
| 707 | * Return the many-to-many extra fields specification for a specific component. |
||
| 708 | * |
||
| 709 | * @param string $class |
||
| 710 | * @param string $component |
||
| 711 | * @return array|null |
||
| 712 | */ |
||
| 713 | public function manyManyExtraFieldsForComponent($class, $component) |
||
| 737 | |||
| 738 | /** |
||
| 739 | * Return data for a specific has_many component. |
||
| 740 | * |
||
| 741 | * @param string $class Parent class |
||
| 742 | * @param string $component |
||
| 743 | * @param bool $classOnly If this is TRUE, than any has_many relationships in the form |
||
| 744 | * "ClassName.Field" will have the field data stripped off. It defaults to TRUE. |
||
| 745 | * @return string|null |
||
| 746 | */ |
||
| 747 | public function hasManyComponent($class, $component, $classOnly = true) |
||
| 762 | |||
| 763 | /** |
||
| 764 | * Return data for a specific has_one component. |
||
| 765 | * |
||
| 766 | * @param string $class |
||
| 767 | * @param string $component |
||
| 768 | * @return string|null |
||
| 769 | */ |
||
| 770 | public function hasOneComponent($class, $component) |
||
| 782 | |||
| 783 | /** |
||
| 784 | * Return data for a specific belongs_to component. |
||
| 785 | * |
||
| 786 | * @param string $class |
||
| 787 | * @param string $component |
||
| 788 | * @param bool $classOnly If this is TRUE, than any has_many relationships in the |
||
| 789 | * form "ClassName.Field" will have the field data stripped off. It defaults to TRUE. |
||
| 790 | * @return string|null |
||
| 791 | */ |
||
| 792 | public function belongsToComponent($class, $component, $classOnly = true) |
||
| 807 | |||
| 808 | /** |
||
| 809 | * |
||
| 810 | * @param string $parentClass Parent class name |
||
| 811 | * @param string $component ManyMany name |
||
| 812 | * @param string|array $specification Declaration of many_many relation type |
||
| 813 | * @return array |
||
| 814 | */ |
||
| 815 | protected function parseManyManyComponent($parentClass, $component, $specification) |
||
| 855 | |||
| 856 | /** |
||
| 857 | * Find a many_many on the child class that points back to this many_many |
||
| 858 | * |
||
| 859 | * @param string $childClass |
||
| 860 | * @param string $parentClass |
||
| 861 | * @return string|null |
||
| 862 | */ |
||
| 863 | protected function getManyManyInverseRelationship($childClass, $parentClass) |
||
| 876 | |||
| 877 | /** |
||
| 878 | * Tries to find the database key on another object that is used to store a |
||
| 879 | * relationship to this class. If no join field can be found it defaults to 'ParentID'. |
||
| 880 | * |
||
| 881 | * If the remote field is polymorphic then $polymorphic is set to true, and the return value |
||
| 882 | * is in the form 'Relation' instead of 'RelationID', referencing the composite DBField. |
||
| 883 | * |
||
| 884 | * @param string $class |
||
| 885 | * @param string $component Name of the relation on the current object pointing to the |
||
| 886 | * remote object. |
||
| 887 | * @param string $type the join type - either 'has_many' or 'belongs_to' |
||
| 888 | * @param boolean $polymorphic Flag set to true if the remote join field is polymorphic. |
||
| 889 | * @return string |
||
| 890 | * @throws Exception |
||
| 891 | */ |
||
| 892 | public function getRemoteJoinField($class, $component, $type = 'has_many', &$polymorphic = false) |
||
| 960 | |||
| 961 | /** |
||
| 962 | * Validate the to or from field on a has_many mapping class |
||
| 963 | * |
||
| 964 | * @param string $parentClass Name of parent class |
||
| 965 | * @param string $component Name of many_many component |
||
| 966 | * @param string $joinClass Class for the joined table |
||
| 967 | * @param array $specification Complete many_many specification |
||
| 968 | * @param string $key Name of key to check ('from' or 'to') |
||
| 969 | * @return string Class that matches the given relation |
||
| 970 | * @throws InvalidArgumentException |
||
| 971 | */ |
||
| 972 | protected function checkManyManyFieldClass($parentClass, $component, $joinClass, $specification, $key) |
||
| 1019 | |||
| 1020 | /** |
||
| 1021 | * @param string $parentClass Name of parent class |
||
| 1022 | * @param string $component Name of many_many component |
||
| 1023 | * @param array $specification Complete many_many specification |
||
| 1024 | * @return string Name of join class |
||
| 1025 | */ |
||
| 1026 | protected function checkManyManyJoinClass($parentClass, $component, $specification) |
||
| 1042 | |||
| 1043 | /** |
||
| 1044 | * Validate a given class is valid for a relation |
||
| 1045 | * |
||
| 1046 | * @param string $class Parent class |
||
| 1047 | * @param string $component Component name |
||
| 1048 | * @param string $relationClass Candidate class to check |
||
| 1049 | * @param string $type Relation type (e.g. has_one) |
||
| 1050 | */ |
||
| 1051 | protected function checkRelationClass($class, $component, $relationClass, $type) |
||
| 1081 | } |
||
| 1082 |
In PHP, under loose comparison (like
==, or!=, orswitchconditions), values of different types might be equal.For
stringvalues, the empty string''is a special case, in particular the following results might be unexpected: