Complex classes like Image_XMP 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 Image_XMP, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
34 | class Image_XMP |
||
35 | { |
||
36 | /** |
||
37 | * @var string |
||
38 | * The name of the image file that contains the XMP fields to extract and modify. |
||
39 | * @see Image_XMP() |
||
40 | */ |
||
41 | public $_sFilename = null; |
||
42 | |||
43 | /** |
||
44 | * @var array |
||
45 | * The XMP fields that were extracted from the image or updated by this class. |
||
46 | * @see getAllTags() |
||
47 | */ |
||
48 | public $_aXMP = array(); |
||
49 | |||
50 | /** |
||
51 | * @var boolean |
||
52 | * True if an APP1 segment was found to contain XMP metadata. |
||
53 | * @see isValid() |
||
54 | */ |
||
55 | public $_bXMPParse = false; |
||
56 | |||
57 | /** |
||
58 | * Returns the status of XMP parsing during instantiation |
||
59 | * |
||
60 | * You'll normally want to call this method before trying to get XMP fields. |
||
61 | * |
||
62 | * @return boolean |
||
63 | * Returns true if an APP1 segment was found to contain XMP metadata. |
||
64 | */ |
||
65 | public function isValid() |
||
69 | |||
70 | /** |
||
71 | * Get a copy of all XMP tags extracted from the image |
||
72 | * |
||
73 | * @return array - An array of XMP fields as it extracted by the XMPparse() function |
||
74 | */ |
||
75 | public function getAllTags() |
||
79 | |||
80 | /** |
||
81 | * Reads all the JPEG header segments from an JPEG image file into an array |
||
82 | * |
||
83 | * @param string $filename - the filename of the JPEG file to read |
||
84 | * @return array|boolean $headerdata - Array of JPEG header segments, |
||
85 | * FALSE - if headers could not be read |
||
86 | */ |
||
87 | public function _get_jpeg_header_data($filename) |
||
189 | |||
190 | |||
191 | /** |
||
192 | * Retrieves XMP information from an APP1 JPEG segment and returns the raw XML text as a string. |
||
193 | * |
||
194 | * @param string $filename - the filename of the JPEG file to read |
||
195 | * @return string|boolean $xmp_data - the string of raw XML text, |
||
196 | * FALSE - if an APP 1 XMP segment could not be found, or if an error occured |
||
197 | */ |
||
198 | public function _get_XMP_text($filename) |
||
222 | |||
223 | /** |
||
224 | * Parses a string containing XMP data (XML), and returns an array |
||
225 | * which contains all the XMP (XML) information. |
||
226 | * |
||
227 | * @param string $xmltext - a string containing the XMP data (XML) to be parsed |
||
228 | * @return array|boolean $xmp_array - an array containing all xmp details retrieved, |
||
229 | * FALSE - couldn't parse the XMP data. |
||
230 | */ |
||
231 | public function read_XMP_array_from_text($xmltext) |
||
232 | { |
||
233 | // Check if there actually is any text to parse |
||
234 | if (trim($xmltext) == '') |
||
235 | { |
||
236 | return false; |
||
237 | } |
||
238 | |||
239 | // Create an instance of a xml parser to parse the XML text |
||
240 | $xml_parser = xml_parser_create('UTF-8'); |
||
241 | |||
242 | // Change: Fixed problem that caused the whitespace (especially newlines) to be destroyed when converting xml text to an xml array, as of revision 1.10 |
||
243 | |||
244 | // We would like to remove unneccessary white space, but this will also |
||
245 | // remove things like newlines (
) in the XML values, so white space |
||
246 | // will have to be removed later |
||
247 | if (xml_parser_set_option($xml_parser, XML_OPTION_SKIP_WHITE, 0) == false) |
||
248 | { |
||
249 | // Error setting case folding - destroy the parser and return |
||
250 | xml_parser_free($xml_parser); |
||
251 | return false; |
||
252 | } |
||
253 | |||
254 | // to use XML code correctly we have to turn case folding |
||
255 | // (uppercasing) off. XML is case sensitive and upper |
||
256 | // casing is in reality XML standards violation |
||
257 | if (xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, 0) == false) |
||
258 | { |
||
259 | // Error setting case folding - destroy the parser and return |
||
260 | xml_parser_free($xml_parser); |
||
261 | return false; |
||
262 | } |
||
263 | |||
264 | // Parse the XML text into a array structure |
||
265 | if (xml_parse_into_struct($xml_parser, $xmltext, $values, $tags) == 0) |
||
266 | { |
||
267 | // Error Parsing XML - destroy the parser and return |
||
268 | xml_parser_free($xml_parser); |
||
269 | return false; |
||
270 | } |
||
271 | |||
272 | // Destroy the xml parser |
||
273 | xml_parser_free($xml_parser); |
||
274 | |||
275 | // Clear the output array |
||
276 | $xmp_array = array(); |
||
277 | |||
278 | // The XMP data has now been parsed into an array ... |
||
279 | |||
280 | // Cycle through each of the array elements |
||
281 | $current_property = ''; // current property being processed |
||
282 | $container_index = -1; // -1 = no container open, otherwise index of container content |
||
283 | foreach ($values as $xml_elem) |
||
284 | { |
||
285 | // Syntax and Class names |
||
286 | switch ($xml_elem['tag']) |
||
287 | { |
||
288 | case 'x:xmpmeta': |
||
289 | // only defined attribute is x:xmptk written by Adobe XMP Toolkit; value is the version of the toolkit |
||
290 | break; |
||
291 | |||
292 | case 'rdf:RDF': |
||
293 | // required element immediately within x:xmpmeta; no data here |
||
294 | break; |
||
295 | |||
296 | case 'rdf:Description': |
||
297 | switch ($xml_elem['type']) |
||
298 | { |
||
299 | case 'open': |
||
300 | case 'complete': |
||
301 | if (array_key_exists('attributes', $xml_elem)) |
||
302 | { |
||
303 | // rdf:Description may contain wanted attributes |
||
304 | foreach (array_keys($xml_elem['attributes']) as $key) |
||
305 | { |
||
306 | // Check whether we want this details from this attribute |
||
307 | // if (in_array($key, $GLOBALS['XMP_tag_captions'])) |
||
308 | // if (true) |
||
309 | // { |
||
310 | // Attribute wanted |
||
311 | $xmp_array[$key] = $xml_elem['attributes'][$key]; |
||
312 | // } |
||
313 | } |
||
314 | } |
||
315 | break; |
||
316 | case 'cdata': |
||
317 | case 'close': |
||
318 | break; |
||
319 | } |
||
320 | break; |
||
321 | |||
322 | case 'rdf:ID': |
||
323 | case 'rdf:nodeID': |
||
324 | // Attributes are ignored |
||
325 | break; |
||
326 | |||
327 | case 'rdf:li': |
||
328 | // Property member |
||
329 | if ($xml_elem['type'] == 'complete') |
||
330 | { |
||
331 | if (array_key_exists('attributes', $xml_elem)) |
||
332 | { |
||
333 | // If Lang Alt (language alternatives) then ensure we take the default language |
||
334 | if (isset($xml_elem['attributes']['xml:lang']) && ($xml_elem['attributes']['xml:lang'] != 'x-default')) |
||
335 | { |
||
336 | break; |
||
337 | } |
||
338 | } |
||
339 | if ($current_property != '') |
||
340 | { |
||
341 | $xmp_array[$current_property][$container_index] = (isset($xml_elem['value']) ? $xml_elem['value'] : ''); |
||
342 | $container_index += 1; |
||
343 | } |
||
344 | //else unidentified attribute!! |
||
345 | } |
||
346 | break; |
||
347 | |||
348 | case 'rdf:Seq': |
||
349 | case 'rdf:Bag': |
||
350 | case 'rdf:Alt': |
||
351 | // Container found |
||
352 | switch ($xml_elem['type']) |
||
353 | { |
||
354 | case 'open': |
||
355 | $container_index = 0; |
||
356 | break; |
||
357 | case 'close': |
||
358 | $container_index = -1; |
||
359 | break; |
||
360 | case 'cdata': |
||
361 | break; |
||
362 | } |
||
363 | break; |
||
364 | |||
365 | default: |
||
366 | // Check whether we want the details from this attribute |
||
367 | // if (in_array($xml_elem['tag'], $GLOBALS['XMP_tag_captions'])) |
||
368 | // if (true) |
||
369 | // { |
||
370 | switch ($xml_elem['type']) |
||
371 | { |
||
372 | case 'open': |
||
373 | // open current element |
||
374 | $current_property = $xml_elem['tag']; |
||
375 | break; |
||
376 | |||
377 | case 'close': |
||
378 | // close current element |
||
379 | $current_property = ''; |
||
380 | break; |
||
381 | |||
382 | case 'complete': |
||
383 | // store attribute value |
||
384 | $xmp_array[$xml_elem['tag']] = (isset($xml_elem['attributes']) ? $xml_elem['attributes'] : (isset($xml_elem['value']) ? $xml_elem['value'] : '')); |
||
385 | break; |
||
386 | |||
387 | case 'cdata': |
||
388 | // ignore |
||
389 | break; |
||
390 | } |
||
391 | // } |
||
392 | break; |
||
393 | } |
||
394 | |||
395 | } |
||
396 | return $xmp_array; |
||
397 | } |
||
398 | |||
399 | |||
400 | /** |
||
401 | * Constructor |
||
402 | * |
||
403 | * @param string $sFilename - Name of the image file to access and extract XMP information from. |
||
404 | */ |
||
405 | public function __construct($sFilename) |
||
423 | |||
424 | } |
||
425 | |||
775 |
If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration: