Complex classes like UserAPI 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 UserAPI, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
32 | class UserAPI extends CAT { |
||
33 | |||
34 | /** |
||
35 | * nothing special to be done here. |
||
36 | */ |
||
37 | public function __construct() { |
||
40 | |||
41 | /** |
||
42 | * Prepare the device module environment and send back the link |
||
43 | * This method creates a device module instance via the {@link DeviceFactory} call, |
||
44 | * then sets up the device module environment for the specific profile by calling |
||
45 | * {@link DeviceConfig::setup()} method and finally, called the devide writeInstaller meethod |
||
46 | * passing the returned path name. |
||
47 | * |
||
48 | * @param string $device identifier as in {@link devices.php} |
||
49 | * @param int $profileId profile identifier |
||
50 | * |
||
51 | * @return array |
||
52 | * array with the following fields: |
||
53 | * profile - the profile identifier; |
||
54 | * device - the device identifier; |
||
55 | * link - the path name of the resulting installer |
||
56 | * mime - the mimetype of the installer |
||
57 | */ |
||
58 | public function generateInstaller($device, $profileId, $generatedFor = "user", $token = NULL, $password = NULL) { |
||
86 | |||
87 | private function verifyDownloadAccess($profile) { |
||
88 | $attribs = $profile->getCollapsedAttributes(); |
||
89 | if (\core\common\Entity::getAttributeValue($attribs, 'profile:production', 0) !== 'on') { |
||
90 | $this->loggerInstance->debug(4, "Attempt to download a non-production ready installer for profile: $profile->identifier\n"); |
||
91 | $auth = new \web\lib\admin\Authentication(); |
||
92 | if (!$auth->isAuthenticated()) { |
||
93 | $this->loggerInstance->debug(2, "User NOT authenticated, rejecting request for a non-production installer\n"); |
||
94 | header("HTTP/1.0 403 Not Authorized"); |
||
95 | return(FALSE); |
||
96 | } |
||
97 | $userObject = new User($_SESSION['user']); |
||
98 | if (!$userObject->isIdPOwner($profile->institution)) { |
||
99 | $this->loggerInstance->debug(2, "User not an owner of a non-production profile - access forbidden\n"); |
||
100 | header("HTTP/1.0 403 Not Authorized"); |
||
101 | return(FALSE); |
||
102 | } |
||
103 | $this->loggerInstance->debug(4, "User is the owner - allowing access\n"); |
||
104 | } |
||
105 | return(TRUE); |
||
106 | } |
||
107 | |||
108 | /** |
||
109 | * This function tries to find a cached copy of an installer for a given |
||
110 | * combination of Profile and device |
||
111 | * @param string $device |
||
112 | * @param AbstractProfile $profile |
||
113 | * @return boolean|string the string with the path to the cached copy, or FALSE if no cached copy exists |
||
114 | */ |
||
115 | private function getCache($device, $profile) { |
||
134 | |||
135 | /** |
||
136 | * Generates a new installer for the given combination of device and Profile |
||
137 | * |
||
138 | * @param string $device |
||
139 | * @param AbstractProfile $profile |
||
140 | * @return array info about the new installer (mime and link) |
||
141 | */ |
||
142 | private function generateNewInstaller($device, $profile, $generatedFor, $token, $password) { |
||
177 | |||
178 | /** |
||
179 | * interface to Devices::listDevices() |
||
180 | */ |
||
181 | public function listDevices($showHidden = 0) { |
||
202 | |||
203 | /** |
||
204 | * |
||
205 | * @param string $device |
||
206 | * @param int $profileId |
||
207 | */ |
||
208 | public function deviceInfo($device, $profileId) { |
||
222 | |||
223 | /** |
||
224 | * Prepare the support data for a given profile |
||
225 | * |
||
226 | * @param int $profId profile identifier |
||
227 | * @return array |
||
228 | * array with the following fields: |
||
229 | * - local_email |
||
230 | * - local_phone |
||
231 | * - local_url |
||
232 | * - description |
||
233 | * - devices - an array of device names and their statuses (for a given profile) |
||
234 | */ |
||
235 | public function profileAttributes($profId) { |
||
258 | |||
259 | /** |
||
260 | * Generate and send the installer |
||
261 | * |
||
262 | * @param string $device identifier as in {@link devices.php} |
||
263 | * @param int $prof_id profile identifier |
||
264 | * @return string binary stream: installerFile |
||
265 | */ |
||
266 | public function downloadInstaller($device, $prof_id, $generated_for = 'user', $token = NULL, $password = NULL) { |
||
288 | |||
289 | /** |
||
290 | * resizes image files |
||
291 | * |
||
292 | * @param string $inputImage |
||
293 | * @param string $destFile |
||
294 | * @param int $width |
||
295 | * @param int $height |
||
296 | * @param bool $resize shall we do resizing? width and height are ignored otherwise |
||
297 | * @return array |
||
298 | */ |
||
299 | private function processImage($inputImage, $destFile, $width, $height, $resize) { |
||
319 | |||
320 | /** |
||
321 | * Get and prepare logo file |
||
322 | * |
||
323 | * When called for DiscoJuice, first check if file cache exists |
||
324 | * If not then generate the file and save it in the cache |
||
325 | * @param int $identifier IdP of Federation identifier |
||
326 | * @param string either 'idp' or 'federation' is allowed |
||
327 | * @param int $width maximum width of the generated image - if 0 then it is treated as no upper bound |
||
328 | * @param int $height maximum height of the generated image - if 0 then it is treated as no upper bound |
||
329 | * @return array|null array with image information or NULL if there is no logo |
||
330 | */ |
||
331 | protected function getLogo($identifier, $type, $width = 0, $height = 0) { |
||
372 | |||
373 | private function testForResize($width, $height) { |
||
389 | |||
390 | /** |
||
391 | * find out where the device is currently located |
||
392 | * @return array |
||
393 | */ |
||
394 | public function locateDevice() { |
||
398 | |||
399 | |||
400 | /** |
||
401 | * Lists all identity providers in the database |
||
402 | * adding information required by DiscoJuice. |
||
403 | * |
||
404 | * @param int $activeOnly if set to non-zero will cause listing of only those institutions which have some valid profiles defined. |
||
405 | * @param string $country if set, only list IdPs in a specific country |
||
406 | * @return array the list of identity providers |
||
407 | * |
||
408 | */ |
||
409 | public function listAllIdentityProviders($activeOnly = 0, $country = "") { |
||
412 | |||
413 | /** |
||
414 | * Order active identity providers according to their distance and name |
||
415 | * @param array $currentLocation - current location |
||
416 | * @return array $IdPs - list of arrays ('id', 'name'); |
||
417 | */ |
||
418 | public function orderIdentityProviders($country, $currentLocation = NULL) { |
||
421 | |||
422 | /** |
||
423 | * Detect the best device driver form the browser |
||
424 | * Detects the operating system and returns its id |
||
425 | * display name and group membership (as in devices.php) |
||
426 | * @return array|FALSE OS information, indexed by 'id', 'display', 'group' |
||
427 | */ |
||
428 | public function detectOS() { |
||
453 | |||
454 | /* |
||
455 | * test if devise is defined and is not hidden. If all is fine return extracted information. |
||
456 | * Return FALSE if the device has not been correctly specified |
||
457 | */ |
||
458 | private function returnDevice($devId, $device) { |
||
465 | |||
466 | /** |
||
467 | * This methods cheks if the devide has been specified as the HTTP parameters |
||
468 | * @return device id|FALSE if correcty specified or FALSE otherwise |
||
469 | */ |
||
470 | private function deviceFromRequest() { |
||
483 | |||
484 | /** |
||
485 | * finds all the user certificates that originated in a given token |
||
486 | * @param string $token |
||
487 | * @return array|false returns FALSE if a token is invalid, otherwise array of certs |
||
488 | */ |
||
489 | public function getUserCerts($token) { |
||
507 | |||
508 | public $device; |
||
509 | private $installerPath; |
||
510 | |||
511 | /** |
||
512 | * helper function to sort profiles by their name |
||
513 | * @param \core\AbstractProfile $profile1 the first profile's information |
||
514 | * @param \core\AbstractProfile $profile2 the second profile's information |
||
515 | * @return int |
||
516 | */ |
||
517 | private static function profileSort($profile1, $profile2) { |
||
520 | |||
521 | } |
||
522 |
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.