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 Paste 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 Paste, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
25 | class Paste extends AbstractModel |
||
26 | { |
||
27 | /** |
||
28 | * Get paste data. |
||
29 | * |
||
30 | * @access public |
||
31 | * @throws Exception |
||
32 | * @return stdClass |
||
33 | */ |
||
34 | 40 | public function get() |
|
35 | { |
||
36 | 40 | $data = $this->_store->read($this->getId()); |
|
37 | 40 | if ($data === false) { |
|
38 | 1 | throw new Exception(PrivateBin::GENERIC_ERROR, 64); |
|
39 | } |
||
40 | |||
41 | // check if paste has expired and delete it if neccessary. |
||
42 | 39 | if (property_exists($data->meta, 'expire_date')) { |
|
43 | 5 | if ($data->meta->expire_date < time()) { |
|
44 | 4 | $this->delete(); |
|
45 | 4 | throw new Exception(PrivateBin::GENERIC_ERROR, 63); |
|
46 | } |
||
47 | // We kindly provide the remaining time before expiration (in seconds) |
||
48 | 1 | $data->meta->remaining_time = $data->meta->expire_date - time(); |
|
49 | } |
||
50 | |||
51 | // check if non-expired burn after reading paste needs to be deleted |
||
52 | 35 | if (property_exists($data->meta, 'burnafterreading') && $data->meta->burnafterreading && $this->_conf->getKey('instantburnafterreading')) { |
|
53 | 2 | $this->delete(); |
|
54 | } |
||
55 | |||
56 | // set formatter for for the view. |
||
57 | 35 | if (!property_exists($data->meta, 'formatter')) { |
|
58 | // support < 0.21 syntax highlighting |
||
59 | 6 | if (property_exists($data->meta, 'syntaxcoloring') && $data->meta->syntaxcoloring === true) { |
|
60 | 2 | $data->meta->formatter = 'syntaxhighlighting'; |
|
61 | } else { |
||
62 | 4 | $data->meta->formatter = $this->_conf->getKey('defaultformatter'); |
|
63 | } |
||
64 | } |
||
65 | |||
66 | // support old paste format with server wide salt |
||
67 | 35 | if (!property_exists($data->meta, 'salt')) { |
|
68 | 4 | $data->meta->salt = ServerSalt::get(); |
|
69 | } |
||
70 | 35 | $data->comments = array_values($this->getComments()); |
|
71 | 35 | $data->comment_count = count($data->comments); |
|
72 | 35 | $data->comment_offset = 0; |
|
73 | 35 | $data->{'@context'} = 'js/paste.jsonld'; |
|
74 | 35 | $this->_data = $data; |
|
75 | 35 | return $this->_data; |
|
76 | } |
||
77 | |||
78 | /** |
||
79 | * Store the paste's data. |
||
80 | * |
||
81 | * @access public |
||
82 | * @throws Exception |
||
83 | */ |
||
84 | 35 | public function store() |
|
85 | { |
||
86 | // Check for improbable collision. |
||
87 | 35 | if ($this->exists()) { |
|
88 | 3 | throw new Exception('You are unlucky. Try again.', 75); |
|
89 | } |
||
90 | |||
91 | 33 | $this->_data->meta->postdate = time(); |
|
92 | 33 | $this->_data->meta->salt = serversalt::generate(); |
|
93 | |||
94 | // store paste |
||
95 | if ( |
||
|
|||
96 | 33 | $this->_store->create( |
|
97 | 33 | $this->getId(), |
|
98 | 33 | json_decode(json_encode($this->_data), true) |
|
99 | 33 | ) === false |
|
100 | ) { |
||
101 | throw new Exception('Error saving paste. Sorry.', 76); |
||
102 | } |
||
103 | 33 | } |
|
104 | |||
105 | /** |
||
106 | * Delete the paste. |
||
107 | * |
||
108 | * @access public |
||
109 | * @throws Exception |
||
110 | */ |
||
111 | 26 | public function delete() |
|
112 | { |
||
113 | 26 | $this->_store->delete($this->getId()); |
|
114 | 26 | } |
|
115 | |||
116 | /** |
||
117 | * Test if paste exists in store. |
||
118 | * |
||
119 | * @access public |
||
120 | * @return bool |
||
121 | */ |
||
122 | 84 | public function exists() |
|
123 | { |
||
124 | 84 | return $this->_store->exists($this->getId()); |
|
125 | } |
||
126 | |||
127 | /** |
||
128 | * Get a comment, optionally a specific instance. |
||
129 | * |
||
130 | * @access public |
||
131 | * @param string $parentId |
||
132 | * @param string $commentId |
||
133 | * @throws Exception |
||
134 | * @return Comment |
||
135 | */ |
||
136 | 20 | public function getComment($parentId, $commentId = null) |
|
137 | { |
||
138 | 20 | if (!$this->exists()) { |
|
139 | 1 | throw new Exception('Invalid data.', 62); |
|
140 | } |
||
141 | 19 | $comment = new Comment($this->_conf, $this->_store); |
|
142 | 19 | $comment->setPaste($this); |
|
143 | 19 | $comment->setParentId($parentId); |
|
144 | 17 | if ($commentId !== null) { |
|
145 | 5 | $comment->setId($commentId); |
|
146 | } |
||
147 | 17 | return $comment; |
|
148 | } |
||
149 | |||
150 | /** |
||
151 | * Get all comments, if any. |
||
152 | * |
||
153 | * @access public |
||
154 | * @return array |
||
155 | */ |
||
156 | 35 | public function getComments() |
|
157 | { |
||
158 | 35 | return $this->_store->readComments($this->getId()); |
|
159 | } |
||
160 | |||
161 | /** |
||
162 | * Generate the "delete" token. |
||
163 | * |
||
164 | * The token is the hmac of the pastes ID signed with the server salt. |
||
165 | * The paste can be deleted by calling: |
||
166 | * http://example.com/privatebin/?pasteid=<pasteid>&deletetoken=<deletetoken> |
||
167 | * |
||
168 | * @access public |
||
169 | * @return string |
||
170 | */ |
||
171 | 32 | public function getDeleteToken() |
|
172 | { |
||
173 | 32 | if (!property_exists($this->_data->meta, 'salt')) { |
|
174 | $this->get(); |
||
175 | } |
||
176 | 32 | return hash_hmac( |
|
177 | 32 | $this->_conf->getKey('zerobincompatibility') ? 'sha1' : 'sha256', |
|
178 | 32 | $this->getId(), |
|
179 | 32 | $this->_data->meta->salt |
|
180 | ); |
||
181 | } |
||
182 | |||
183 | /** |
||
184 | * Set paste's attachment. |
||
185 | * |
||
186 | * @access public |
||
187 | * @param string $attachment |
||
188 | * @throws Exception |
||
189 | */ |
||
190 | 2 | View Code Duplication | public function setAttachment($attachment) |
191 | { |
||
192 | 2 | if (!$this->_conf->getKey('fileupload') || !Sjcl::isValid($attachment)) { |
|
193 | throw new Exception('Invalid attachment.', 71); |
||
194 | } |
||
195 | 2 | $this->_data->meta->attachment = $attachment; |
|
196 | 2 | } |
|
197 | |||
198 | /** |
||
199 | * Set paste's attachment name. |
||
200 | * |
||
201 | * @access public |
||
202 | * @param string $attachmentname |
||
203 | * @throws Exception |
||
204 | */ |
||
205 | 2 | View Code Duplication | public function setAttachmentName($attachmentname) |
206 | { |
||
207 | 2 | if (!$this->_conf->getKey('fileupload') || !Sjcl::isValid($attachmentname)) { |
|
208 | throw new Exception('Invalid attachment.', 72); |
||
209 | } |
||
210 | 2 | $this->_data->meta->attachmentname = $attachmentname; |
|
211 | 2 | } |
|
212 | |||
213 | /** |
||
214 | * Set paste expiration. |
||
215 | * |
||
216 | * @access public |
||
217 | * @param string $expiration |
||
218 | */ |
||
219 | 7 | public function setExpiration($expiration) |
|
232 | |||
233 | /** |
||
234 | * Set paste's burn-after-reading type. |
||
235 | * |
||
236 | * @access public |
||
237 | * @param string $burnafterreading |
||
238 | * @throws Exception |
||
239 | */ |
||
240 | 3 | public function setBurnafterreading($burnafterreading = '1') |
|
252 | |||
253 | /** |
||
254 | * Set paste's discussion state. |
||
255 | * |
||
256 | * @access public |
||
257 | * @param string $opendiscussion |
||
258 | * @throws Exception |
||
259 | */ |
||
260 | 11 | public function setOpendiscussion($opendiscussion = '1') |
|
261 | { |
||
275 | |||
276 | /** |
||
277 | * Set paste's format. |
||
278 | * |
||
279 | * @access public |
||
280 | * @param string $format |
||
281 | * @throws Exception |
||
282 | */ |
||
283 | 8 | public function setFormatter($format) |
|
290 | |||
291 | /** |
||
292 | * Check if paste is of burn-after-reading type. |
||
293 | * |
||
294 | * @access public |
||
295 | * @throws Exception |
||
296 | * @return bool |
||
297 | */ |
||
298 | 25 | View Code Duplication | public function isBurnafterreading() |
306 | |||
307 | /** |
||
308 | * Check if paste has discussions enabled. |
||
309 | * |
||
310 | * @access public |
||
311 | * @throws Exception |
||
312 | * @return bool |
||
313 | */ |
||
314 | 13 | View Code Duplication | public function isOpendiscussion() |
322 | } |
||
323 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.