Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like FieldUpload 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 FieldUpload, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
10 | class FieldUpload extends Field implements ExportableField, ImportableField |
||
11 | { |
||
12 | protected static $imageMimeTypes = array( |
||
13 | 'image/gif', |
||
14 | 'image/jpg', |
||
15 | 'image/jpeg', |
||
16 | 'image/pjpeg', |
||
17 | 'image/png', |
||
18 | 'image/x-png' |
||
19 | ); |
||
20 | |||
21 | View Code Duplication | public function __construct() |
|
22 | { |
||
23 | parent::__construct(); |
||
24 | |||
25 | $this->_name = __('File Upload'); |
||
|
|||
26 | $this->_required = true; |
||
27 | |||
28 | $this->set('location', 'sidebar'); |
||
29 | $this->set('required', 'no'); |
||
30 | } |
||
31 | |||
32 | /*------------------------------------------------------------------------- |
||
33 | Definition: |
||
34 | -------------------------------------------------------------------------*/ |
||
35 | |||
36 | public function canFilter() |
||
40 | |||
41 | public function canPrePopulate() |
||
45 | |||
46 | public function isSortable() |
||
50 | |||
51 | public function fetchFilterableOperators() |
||
52 | { |
||
53 | return array( |
||
54 | array( |
||
55 | 'title' => 'is', |
||
56 | 'filter' => ' ', |
||
57 | 'help' => __('Find files that are an exact match for the given string.') |
||
58 | ), |
||
59 | array( |
||
60 | 'title' => 'contains', |
||
61 | 'filter' => 'regexp: ', |
||
62 | 'help' => __('Find files that match the given <a href="%s">MySQL regular expressions</a>.', array( |
||
63 | 'http://dev.mysql.com/doc/mysql/en/Regexp.html' |
||
64 | )) |
||
65 | ), |
||
66 | array( |
||
67 | 'title' => 'does not contain', |
||
68 | 'filter' => 'not-regexp: ', |
||
69 | 'help' => __('Find files that do not match the given <a href="%s">MySQL regular expressions</a>.', array( |
||
70 | 'http://dev.mysql.com/doc/mysql/en/Regexp.html' |
||
71 | )) |
||
72 | ), |
||
73 | array( |
||
74 | 'title' => 'file type is', |
||
75 | 'filter' => 'mimetype: ', |
||
76 | 'help' => __('Find files that match the given mimetype.') |
||
77 | ), |
||
78 | array( |
||
79 | 'title' => 'size is', |
||
80 | 'filter' => 'size: ', |
||
81 | 'help' => __('Find files that match the given size.') |
||
82 | ) |
||
83 | ); |
||
84 | } |
||
85 | |||
86 | /*------------------------------------------------------------------------- |
||
87 | Setup: |
||
88 | -------------------------------------------------------------------------*/ |
||
89 | |||
90 | public function createTable() |
||
91 | { |
||
92 | return Symphony::Database()->query( |
||
93 | "CREATE TABLE IF NOT EXISTS `tbl_entries_data_" . $this->get('id') . "` ( |
||
94 | `id` int(11) unsigned NOT null auto_increment, |
||
95 | `entry_id` int(11) unsigned NOT null, |
||
96 | `file` varchar(255) default null, |
||
97 | `size` int(11) unsigned null, |
||
98 | `mimetype` varchar(100) default null, |
||
99 | `meta` varchar(255) default null, |
||
100 | PRIMARY KEY (`id`), |
||
101 | UNIQUE KEY `entry_id` (`entry_id`), |
||
102 | KEY `file` (`file`), |
||
103 | KEY `mimetype` (`mimetype`) |
||
104 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;" |
||
105 | ); |
||
106 | } |
||
107 | |||
108 | /*------------------------------------------------------------------------- |
||
109 | Utilities: |
||
110 | -------------------------------------------------------------------------*/ |
||
111 | |||
112 | public function entryDataCleanup($entry_id, $data = null) |
||
113 | { |
||
114 | $file_location = $this->getFilePath($data['file']); |
||
115 | |||
116 | if (is_file($file_location)) { |
||
117 | General::deleteFile($file_location); |
||
118 | } |
||
119 | |||
120 | parent::entryDataCleanup($entry_id); |
||
121 | |||
122 | return true; |
||
123 | } |
||
124 | |||
125 | public static function getMetaInfo($file, $type) |
||
126 | { |
||
127 | $meta = array(); |
||
128 | |||
129 | if (!file_exists($file) || !is_readable($file)) { |
||
130 | return $meta; |
||
131 | } |
||
132 | |||
133 | $meta['creation'] = DateTimeObj::get('c', filemtime($file)); |
||
134 | |||
135 | if (General::in_iarray($type, fieldUpload::$imageMimeTypes) && $array = @getimagesize($file)) { |
||
136 | $meta['width'] = $array[0]; |
||
137 | $meta['height'] = $array[1]; |
||
138 | } |
||
139 | |||
140 | return $meta; |
||
141 | } |
||
142 | |||
143 | public function getFilePath($filename) |
||
144 | { |
||
145 | /** |
||
146 | * Ensure the file exists in the `WORKSPACE` directory |
||
147 | * @link http://getsymphony.com/discuss/issues/view/610/ |
||
148 | */ |
||
149 | $file = WORKSPACE . preg_replace(array('%/+%', '%(^|/)\.\./%', '%\/workspace\/%'), '/', $this->get('destination') . '/' . $filename); |
||
150 | |||
151 | return $file; |
||
152 | } |
||
153 | |||
154 | /*------------------------------------------------------------------------- |
||
155 | Settings: |
||
156 | -------------------------------------------------------------------------*/ |
||
157 | |||
158 | public function displaySettingsPanel(XMLElement &$wrapper, $errors = null) |
||
159 | { |
||
160 | parent::displaySettingsPanel($wrapper, $errors); |
||
161 | |||
162 | // Destination Folder |
||
163 | $ignore = array( |
||
164 | '/workspace/events', |
||
165 | '/workspace/data-sources', |
||
166 | '/workspace/text-formatters', |
||
167 | '/workspace/pages', |
||
168 | '/workspace/utilities' |
||
169 | ); |
||
170 | $directories = General::listDirStructure(WORKSPACE, null, true, DOCROOT, $ignore); |
||
171 | |||
172 | $label = Widget::Label(__('Destination Directory')); |
||
173 | |||
174 | $options = array(); |
||
175 | $options[] = array('/workspace', false, '/workspace'); |
||
176 | |||
177 | if (!empty($directories) && is_array($directories)) { |
||
178 | foreach ($directories as $d) { |
||
179 | $d = '/' . trim($d, '/'); |
||
180 | |||
181 | if (!in_array($d, $ignore)) { |
||
182 | $options[] = array($d, ($this->get('destination') == $d), $d); |
||
183 | } |
||
184 | } |
||
185 | } |
||
186 | |||
187 | $label->appendChild(Widget::Select('fields['.$this->get('sortorder').'][destination]', $options)); |
||
188 | |||
189 | View Code Duplication | if (isset($errors['destination'])) { |
|
190 | $wrapper->appendChild(Widget::Error($label, $errors['destination'])); |
||
191 | } else { |
||
192 | $wrapper->appendChild($label); |
||
193 | } |
||
194 | |||
195 | // Validation rule |
||
196 | $this->buildValidationSelect($wrapper, $this->get('validator'), 'fields['.$this->get('sortorder').'][validator]', 'upload', $errors); |
||
197 | |||
198 | // Requirements and table display |
||
199 | $this->appendStatusFooter($wrapper); |
||
200 | } |
||
201 | |||
202 | public function checkFields(array &$errors, $checkForDuplicates = true) |
||
203 | { |
||
204 | if (is_dir(DOCROOT . $this->get('destination') . '/') === false) { |
||
205 | $errors['destination'] = __('The destination directory, %s, does not exist.', array( |
||
206 | '<code>' . $this->get('destination') . '</code>' |
||
207 | )); |
||
208 | View Code Duplication | } elseif (is_writable(DOCROOT . $this->get('destination') . '/') === false) { |
|
209 | $errors['destination'] = __('The destination directory is not writable.') |
||
210 | . ' ' |
||
211 | . __('Please check permissions on %s.', array( |
||
212 | '<code>' . $this->get('destination') . '</code>' |
||
213 | )); |
||
214 | } |
||
215 | |||
216 | parent::checkFields($errors, $checkForDuplicates); |
||
217 | } |
||
218 | |||
219 | View Code Duplication | public function commit() |
|
238 | |||
239 | /*------------------------------------------------------------------------- |
||
240 | Publish: |
||
241 | -------------------------------------------------------------------------*/ |
||
242 | |||
243 | public function displayPublishPanel(XMLElement &$wrapper, $data = null, $flagWithError = null, $fieldnamePrefix = null, $fieldnamePostfix = null, $entry_id = null) |
||
244 | { |
||
245 | if (is_dir(DOCROOT . $this->get('destination') . '/') === false) { |
||
246 | $flagWithError = __('The destination directory, %s, does not exist.', array( |
||
288 | |||
289 | public function validateFilename($file, &$message) |
||
318 | |||
319 | public function checkPostFieldData($data, &$message, $entry_id = null) |
||
403 | |||
404 | public function processRawFieldData($data, &$status, &$message = null, $simulate = false, $entry_id = null) |
||
564 | |||
565 | /*------------------------------------------------------------------------- |
||
566 | Output: |
||
567 | -------------------------------------------------------------------------*/ |
||
568 | |||
569 | public function appendFormattedElement(XMLElement &$wrapper, $data, $encode = false, $mode = null, $entry_id = null) |
||
598 | |||
599 | public function prepareTableValue($data, XMLElement $link = null, $entry_id = null) |
||
617 | |||
618 | public function prepareTextValue($data, $entry_id = null) |
||
625 | |||
626 | public function prepareAssociationsDrawerXMLElement(Entry $e, array $parent_association, $prepopulate = '') |
||
634 | |||
635 | /*------------------------------------------------------------------------- |
||
636 | Import: |
||
637 | -------------------------------------------------------------------------*/ |
||
638 | |||
639 | public function getImportModes() |
||
646 | |||
647 | View Code Duplication | public function prepareImportValue($data, $mode, $entry_id = null) |
|
660 | |||
661 | /*------------------------------------------------------------------------- |
||
662 | Export: |
||
663 | -------------------------------------------------------------------------*/ |
||
664 | |||
665 | /** |
||
666 | * Return a list of supported export modes for use with `prepareExportValue`. |
||
667 | * |
||
668 | * @return array |
||
669 | */ |
||
670 | public function getExportModes() |
||
678 | |||
679 | /** |
||
680 | * Give the field some data and ask it to return a value using one of many |
||
681 | * possible modes. |
||
682 | * |
||
683 | * @param mixed $data |
||
684 | * @param integer $mode |
||
685 | * @param integer $entry_id |
||
686 | * @return array|string|null |
||
687 | */ |
||
688 | public function prepareExportValue($data, $mode, $entry_id = null) |
||
718 | |||
719 | /*------------------------------------------------------------------------- |
||
720 | Filtering: |
||
721 | -------------------------------------------------------------------------*/ |
||
722 | |||
723 | public function buildDSRetrievalSQL($data, &$joins, &$where, $andOperation = false) |
||
775 | |||
776 | /*------------------------------------------------------------------------- |
||
777 | Sorting: |
||
778 | -------------------------------------------------------------------------*/ |
||
779 | |||
780 | public function buildSortingSQL(&$joins, &$where, &$sort, $order = 'ASC') |
||
797 | |||
798 | /*------------------------------------------------------------------------- |
||
799 | Events: |
||
800 | -------------------------------------------------------------------------*/ |
||
801 | |||
802 | View Code Duplication | public function getExampleFormMarkup() |
|
809 | } |
||
810 |
In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:
Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion: