Total Complexity | 65 |
Total Lines | 499 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like GContacts 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.
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 GContacts, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
30 | class GContacts |
||
31 | { |
||
32 | /** full access to the users contacts */ |
||
33 | public const CONTACTS = "https://www.googleapis.com/auth/contacts"; |
||
34 | /** readonly access to the users contacts */ |
||
35 | public const CONTACTS_READONLY = "https://www.googleapis.com/auth/contacts.readonly"; |
||
36 | /** readonly access to the users other contacts */ |
||
37 | public const CONTACTS_OTHER_READONLY ="https://www.googleapis.com/auth/contacts.other.readonly"; |
||
38 | |||
39 | /** Sort people by when they were changed; older entries first. */ |
||
40 | public const SO_LAST_MODIFIED_ASCENDING ='LAST_MODIFIED_ASCENDING'; |
||
41 | /** Sort people by when they were changed; newer entries first. */ |
||
42 | public const SO_LAST_MODIFIED_DESCENDING = 'LAST_MODIFIED_DESCENDING'; |
||
43 | /** Sort people by first name. */ |
||
44 | public const SO_FIRST_NAME_ASCENDING = 'FIRST_NAME_ASCENDING'; |
||
45 | /** Sort people by last name. */ |
||
46 | public const SO_LAST_NAME_ASCENDING = 'LAST_NAME_ASCENDING'; |
||
47 | |||
48 | /** the default personFields for detail view */ |
||
49 | public const DEF_DETAIL_PERSON_FIELDS = [ |
||
50 | GContact::PF_NAMES, |
||
51 | GContact::PF_ORGANIZATIONS, |
||
52 | GContact::PF_NICKNAMES, |
||
53 | GContact::PF_BIRTHDAYS, |
||
54 | GContact::PF_PHOTOS, |
||
55 | GContact::PF_ADDRESSES, |
||
56 | GContact::PF_EMAIL_ADDRESSES, |
||
57 | GContact::PF_PHONE_NUMBERS, |
||
58 | GContact::PF_GENDERS, |
||
59 | GContact::PF_MEMBERSHIPS, |
||
60 | GContact::PF_METADATA, |
||
61 | GContact::PF_BIOGRAPHIES, |
||
62 | GContact::PF_URLS, |
||
63 | ]; |
||
64 | |||
65 | /** the default personFields for detail view */ |
||
66 | public const DEF_LIST_PERSON_FIELDS = [ |
||
67 | GContact::PF_NAMES, |
||
68 | GContact::PF_ORGANIZATIONS, |
||
69 | GContact::PF_NICKNAMES, |
||
70 | GContact::PF_BIRTHDAYS, |
||
71 | GContact::PF_ADDRESSES, |
||
72 | GContact::PF_EMAIL_ADDRESSES, |
||
73 | GContact::PF_PHONE_NUMBERS, |
||
74 | GContact::PF_MEMBERSHIPS, |
||
75 | GContact::PF_METADATA, |
||
76 | ]; |
||
77 | |||
78 | /** max. pagesize for the list request */ |
||
79 | protected const CONTACTS_MAX_PAGESIZE = 1000; |
||
80 | /** max. pagesize for a search */ |
||
81 | protected const SEARCH_MAX_PAGESIZE = 30; |
||
82 | |||
83 | /** @var array<string> personFields/readMask to be returned by the request */ |
||
84 | protected array $aPersonFields = []; |
||
85 | /** pagesize for a request */ |
||
86 | protected int $iPageSize = 200; |
||
87 | |||
88 | /** @var GClient clients to perform the requests to the api */ |
||
89 | protected GClient $oClient; |
||
90 | |||
91 | /** |
||
92 | * Create instance an pass the clinet for the requests. |
||
93 | * @param GClient $oClient |
||
94 | */ |
||
95 | public function __construct(GClient $oClient) |
||
96 | { |
||
97 | $this->oClient = $oClient; |
||
98 | } |
||
99 | |||
100 | /** |
||
101 | * Add personFields/readMask for next request. |
||
102 | * Can be called multiple and/or by passing an array of personFields. All |
||
103 | * const `GContact::PF_xxxx` can be specified. |
||
104 | * @param string|array<string>|null $fields; if set to null, the internal array is cleared |
||
105 | */ |
||
106 | public function addPersonFields($fields) : void |
||
114 | } |
||
115 | } |
||
116 | |||
117 | /** |
||
118 | * Set the pagesize for reading lists. |
||
119 | * May be limited to the max. pgaesize for the request. |
||
120 | * - contact list: max. pagesize is 1000 |
||
121 | * - search: max. pagesize is 30 |
||
122 | * @param int $iPageSize |
||
123 | */ |
||
124 | public function setPageSize(int $iPageSize) : void |
||
125 | { |
||
126 | $this->iPageSize = $iPageSize; |
||
127 | } |
||
128 | |||
129 | /** |
||
130 | * Get the whole contact list. |
||
131 | * @link https://developers.google.com/people/api/rest/v1/people.connections/list |
||
132 | * @param string $strSortOrder one of the self::SO_xxx constants |
||
133 | * @param string $strGroupResourceName |
||
134 | * @return array<mixed>|false |
||
135 | */ |
||
136 | public function list(string $strSortOrder = self::SO_LAST_NAME_ASCENDING, string $strGroupResourceName = '') |
||
137 | { |
||
138 | $aHeader = [$this->oClient->getAuthHeader()]; |
||
139 | |||
140 | if (count($this->aPersonFields) == 0) { |
||
141 | $this->aPersonFields = self::DEF_LIST_PERSON_FIELDS; |
||
142 | } |
||
143 | |||
144 | $aParams = [ |
||
145 | 'personFields' => implode(',', $this->aPersonFields), |
||
146 | 'sortOrder' => $strSortOrder, |
||
147 | 'pageSize' => ($this->iPageSize > self::CONTACTS_MAX_PAGESIZE ? self::CONTACTS_MAX_PAGESIZE : $this->iPageSize), |
||
148 | ]; |
||
149 | |||
150 | $bEndOfList = false; |
||
151 | $aContactList = []; |
||
152 | while ($bEndOfList === false) { |
||
153 | $strURI = 'https://people.googleapis.com/v1/people/me/connections?' . http_build_query($aParams); |
||
154 | $strResponse = $this->oClient->fetchJsonResponse($strURI, GClient::GET, $aHeader); |
||
155 | if ($strResponse === false) { |
||
156 | return false; |
||
157 | } |
||
158 | $oResponse = json_decode($strResponse, true); |
||
159 | if (is_array($oResponse)) { |
||
160 | if (isset($oResponse['nextPageToken']) && !empty($oResponse['nextPageToken'])) { |
||
161 | $aParams['pageToken'] = $oResponse['nextPageToken']; |
||
162 | } else { |
||
163 | $bEndOfList = true; |
||
164 | } |
||
165 | foreach ($oResponse['connections'] as $aContact) { |
||
166 | if (empty($strGroupResourceName) || GContact::fromArray($aContact)->belongsToGroup($strGroupResourceName)) { |
||
167 | $aContactList[] = $aContact; |
||
168 | } |
||
169 | } |
||
170 | } else { |
||
171 | break; |
||
172 | } |
||
173 | } |
||
174 | return $aContactList; |
||
175 | } |
||
176 | |||
177 | /** |
||
178 | * Search within the contacts. |
||
179 | * The query matches on a contact's `names`, `nickNames`, `emailAddresses`, |
||
180 | * `phoneNumbers`, and `organizations` fields. The search for phone numbers only |
||
181 | * works, if leading '+' and contained '(', ')' or spaces are omitted in the query! |
||
182 | * The query is used to match <b>prefix</B> phrases of the fields on a person. |
||
183 | * For example, a person with name "foo name" matches queries such as "f", "fo", |
||
184 | * "foo", "foo n", "nam", etc., but not "oo n". |
||
185 | * > <b>Note:</b> |
||
186 | * > The count of contacts, the search request returns is limitetd to the |
||
187 | * > pageSize (which is limited itself to max. 30 at all). If there are |
||
188 | * > more contacts in the list that matches the query, unfortunately NO |
||
189 | * > further information about that additional contacts - and how many - are |
||
190 | * > available! |
||
191 | * @link https://developers.google.com/people/api/rest/v1/people/searchContacts |
||
192 | * @param string $strQuery the query to search for |
||
193 | * @return array<mixed>|false |
||
194 | */ |
||
195 | public function search(string $strQuery) |
||
196 | { |
||
197 | $aHeader = [$this->oClient->getAuthHeader()]; |
||
198 | |||
199 | if (count($this->aPersonFields) == 0) { |
||
200 | $this->aPersonFields = self::DEF_LIST_PERSON_FIELDS; |
||
201 | } |
||
202 | |||
203 | $aParams = [ |
||
204 | 'query' => '', |
||
205 | 'readMask' => implode(',', $this->aPersonFields), |
||
206 | 'pageSize' => ($this->iPageSize > self::SEARCH_MAX_PAGESIZE ? self::SEARCH_MAX_PAGESIZE : $this->iPageSize), |
||
207 | ]; |
||
208 | $strURI = 'https://people.googleapis.com/v1/people:searchContacts?' . http_build_query($aParams);; |
||
209 | |||
210 | // 'warmup' request |
||
211 | // Note from google documentation: |
||
212 | // Before searching, clients should send a warmup request with an empty query to update the cache. |
||
213 | // https://developers.google.com/people/v1/contacts#search_the_users_contacts |
||
214 | $this->oClient->fetchJsonResponse($strURI, GClient::GET, $aHeader); |
||
215 | $aParams['query'] = $strQuery; |
||
216 | $strURI = 'https://people.googleapis.com/v1/people:searchContacts?' . http_build_query($aParams);; |
||
217 | |||
218 | $aContactList = false; |
||
219 | if (($strResponse = $this->oClient->fetchJsonResponse($strURI, GClient::GET, $aHeader)) !== false) { |
||
220 | $aContactList = []; |
||
221 | $oResponse = json_decode($strResponse, true); |
||
222 | if (is_array($oResponse) && isset($oResponse['results']) && count($oResponse['results']) > 0) { |
||
223 | foreach ($oResponse['results'] as $aContact) { |
||
224 | $aContactList[] = $aContact['person']; |
||
225 | } |
||
226 | } |
||
227 | } |
||
228 | return $aContactList; |
||
229 | } |
||
230 | |||
231 | /** |
||
232 | * Get the contact specified by its resourceName. |
||
233 | * @link https://developers.google.com/people/api/rest/v1/people/get |
||
234 | * @param string $strResourceName |
||
235 | * @return GContact|false |
||
236 | */ |
||
237 | public function getContact(string $strResourceName) |
||
238 | { |
||
239 | $aHeader = [$this->oClient->getAuthHeader()]; |
||
240 | |||
241 | if (count($this->aPersonFields) == 0) { |
||
242 | $this->aPersonFields = self::DEF_DETAIL_PERSON_FIELDS; |
||
243 | } |
||
244 | |||
245 | $aParams = [ |
||
246 | 'personFields' => implode(',', $this->aPersonFields), |
||
247 | ]; |
||
248 | |||
249 | $strURI = 'https://people.googleapis.com/v1/' . $strResourceName . '?' . http_build_query($aParams);; |
||
250 | $strResponse = $this->oClient->fetchJsonResponse($strURI, GClient::GET, $aHeader); |
||
251 | |||
252 | $result = false; |
||
253 | if ($strResponse !== false) { |
||
254 | $result = GContact::fromJSON($strResponse, $this->aPersonFields); |
||
255 | } |
||
256 | return $result; |
||
257 | } |
||
258 | |||
259 | /** |
||
260 | * Creates a new contact. |
||
261 | * @link https://developers.google.com/people/api/rest/v1/people/createContact |
||
262 | * @param GContact $oContact |
||
263 | * @return GContact|false |
||
264 | */ |
||
265 | public function createContact(GContact $oContact) |
||
266 | { |
||
267 | $aHeader = [ |
||
268 | $this->oClient->getAuthHeader(), |
||
269 | 'Content-Type: application/json', |
||
270 | ]; |
||
271 | |||
272 | if (count($this->aPersonFields) == 0) { |
||
273 | $this->aPersonFields = self::DEF_DETAIL_PERSON_FIELDS; |
||
274 | } |
||
275 | $aParams = ['personFields' => implode(',', $this->getUpdatePersonFields())]; |
||
276 | |||
277 | $result = false; |
||
278 | $data = json_encode($oContact); |
||
279 | if ($data !== false) { |
||
280 | $strURI = 'https://people.googleapis.com/v1/people:createContact/?' . http_build_query($aParams); |
||
281 | $strResponse = $this->oClient->fetchJsonResponse($strURI, GClient::POST, $aHeader, $data); |
||
282 | |||
283 | if ($strResponse !== false) { |
||
284 | $result = GContact::fromJSON($strResponse, $this->aPersonFields); |
||
285 | } |
||
286 | } |
||
287 | return $result; |
||
288 | } |
||
289 | |||
290 | /** |
||
291 | * Updates an existing contact specified by resourceName. |
||
292 | * To prevent the user from data loss, this request fails with an 400 response |
||
293 | * code, if the contact has changed on the server, since it was loaded. |
||
294 | * Reload the data from the server and make the changes again! |
||
295 | * @link https://developers.google.com/people/api/rest/v1/people/updateContact |
||
296 | * @param string $strResourceName |
||
297 | * @param GContact $oContact |
||
298 | * @return GContact|false |
||
299 | */ |
||
300 | public function updateContact(string $strResourceName, GContact $oContact) |
||
301 | { |
||
302 | $aHeader = [ |
||
303 | $this->oClient->getAuthHeader(), |
||
304 | 'Content-Type: application/json', |
||
305 | ]; |
||
306 | |||
307 | if (count($this->aPersonFields) == 0) { |
||
308 | $this->aPersonFields = self::DEF_DETAIL_PERSON_FIELDS; |
||
309 | } |
||
310 | $aParams = ['updatePersonFields' => implode(',', $this->getUpdatePersonFields())]; |
||
311 | |||
312 | $result = false; |
||
313 | $data = json_encode($oContact); |
||
314 | if ($data !== false) { |
||
315 | $strURI = 'https://people.googleapis.com/v1/' . $strResourceName . ':updateContact/?' . http_build_query($aParams); |
||
316 | $strResponse = $this->oClient->fetchJsonResponse($strURI, GClient::PATCH, $aHeader, $data); |
||
317 | |||
318 | if ($strResponse !== false) { |
||
319 | $result = GContact::fromJSON($strResponse, $this->aPersonFields); |
||
320 | } |
||
321 | } |
||
322 | return $result; |
||
323 | } |
||
324 | |||
325 | /** |
||
326 | * Delete the requested contact. |
||
327 | * @link https://developers.google.com/people/api/rest/v1/people/deleteContact |
||
328 | * @param string $strResourceName |
||
329 | * @return bool |
||
330 | */ |
||
331 | public function deleteContact(string $strResourceName) : bool |
||
338 | } |
||
339 | |||
340 | /** |
||
341 | * Set or unset the 'starred' mark for the specified contact. |
||
342 | * The 'starred' mark just means, the contact belongs to the system group `contactGroups/starred`. |
||
343 | * @see GContactGroups::addContactsToGroup() |
||
344 | * @see GContactGroups::removeContactsFromGroup() |
||
345 | * @param string $strResourceName |
||
346 | * @param bool $bSetStarred |
||
347 | * @return bool |
||
348 | */ |
||
349 | public function setContactStarred(string $strResourceName, bool $bSetStarred = true) : bool |
||
358 | } |
||
359 | |||
360 | |||
361 | /** |
||
362 | * Set contact photo from image file. |
||
363 | * Supported types are JPG, PNG, GIF and BMP. |
||
364 | * @link https://developers.google.com/people/api/rest/v1/people/updateContactPhoto |
||
365 | * @param string $strResourceName |
||
366 | * @param string $strFilename |
||
367 | * @return bool |
||
368 | */ |
||
369 | public function setContactPhotoFile(string $strResourceName, string $strFilename) : bool |
||
370 | { |
||
371 | $blobPhoto = ''; |
||
372 | if (filter_var($strFilename, FILTER_VALIDATE_URL)) { |
||
373 | $blobPhoto = $this->loadImageFromURL($strFilename); |
||
374 | } elseif (file_exists($strFilename)) { |
||
375 | $blobPhoto = $this->loadImageFromFile($strFilename); |
||
376 | } else { |
||
377 | $this->oClient->setError(0, 'File not found: ' . $strFilename, 'INVALID_ARGUMENT'); |
||
378 | } |
||
379 | $result = false; |
||
380 | if (!empty($blobPhoto)) { |
||
381 | $aHeader = [ |
||
382 | $this->oClient->getAuthHeader(), |
||
383 | 'Content-Type: application/json', |
||
384 | ]; |
||
385 | $data = json_encode(['photoBytes' => $blobPhoto]); |
||
386 | if ($data !== false) { |
||
387 | $strURI = 'https://people.googleapis.com/v1/' . $strResourceName . ':updateContactPhoto'; |
||
388 | $strResponse = $this->oClient->fetchJsonResponse($strURI, GClient::PATCH, $aHeader, $data); |
||
389 | $result = ($strResponse !== false); |
||
390 | } |
||
391 | } |
||
392 | return $result; |
||
393 | } |
||
394 | |||
395 | /** |
||
396 | * Set contact photo from base64 encoded image data. |
||
397 | * @link https://developers.google.com/people/api/rest/v1/people/updateContactPhoto |
||
398 | * @param string $strResourceName |
||
399 | * @param string $blobPhoto base64 encoded image |
||
400 | * @return bool |
||
401 | */ |
||
402 | public function setContactPhoto(string $strResourceName, string $blobPhoto) : bool |
||
403 | { |
||
404 | $result = false; |
||
405 | // check for valid base64 encoded imagedata |
||
406 | $img = base64_decode($blobPhoto); |
||
407 | if ($img !== false && imagecreatefromstring(base64_decode($blobPhoto)) !== false) { |
||
408 | $aHeader = [ |
||
409 | $this->oClient->getAuthHeader(), |
||
410 | 'Content-Type: application/json', |
||
411 | ]; |
||
412 | $data = json_encode(['photoBytes' => $blobPhoto]); |
||
413 | if ($data !== false) { |
||
414 | $strURI = 'https://people.googleapis.com/v1/' . $strResourceName . ':updateContactPhoto'; |
||
415 | $strResponse = $this->oClient->fetchJsonResponse($strURI, GClient::PATCH, $aHeader, $data); |
||
416 | $result = ($strResponse !== false); |
||
417 | } |
||
418 | } else { |
||
419 | $this->oClient->setError(0, 'Invalid base64 encoded image data', 'INVALID_ARGUMENT'); |
||
420 | } |
||
421 | return $result; |
||
422 | } |
||
423 | |||
424 | /** |
||
425 | * Delete contact photo for given contact. |
||
426 | * @link https://developers.google.com/people/api/rest/v1/people/deleteContactPhoto |
||
427 | * @param string $strResourceName |
||
428 | * @return bool |
||
429 | */ |
||
430 | public function deleteContactPhoto(string $strResourceName) : bool |
||
431 | { |
||
432 | $aHeader = [$this->oClient->getAuthHeader()]; |
||
433 | |||
434 | $strURI = 'https://people.googleapis.com/v1/' . $strResourceName . ':deleteContactPhoto'; |
||
435 | $strResponse = $this->oClient->fetchJsonResponse($strURI, GClient::DELETE, $aHeader); |
||
436 | |||
437 | return ($strResponse !== false); |
||
438 | } |
||
439 | |||
440 | /** |
||
441 | * Get updateable personFields. |
||
442 | * @return array<string> |
||
443 | */ |
||
444 | private function getUpdatePersonFields() : array |
||
445 | { |
||
446 | $aReadonlyPersonFields = [ |
||
447 | GContact::PF_PHOTOS, |
||
448 | GContact::PF_COVER_PHOTOS, |
||
449 | GContact::PF_AGE_RANGES, |
||
450 | GContact::PF_METADATA, |
||
451 | ]; |
||
452 | $aUpdatePersonFields = $this->aPersonFields; |
||
453 | foreach ($aReadonlyPersonFields as $strReadonly) { |
||
454 | if (($key = array_search($strReadonly, $aUpdatePersonFields)) !== false) { |
||
455 | unset($aUpdatePersonFields[$key]); |
||
456 | } |
||
457 | } |
||
458 | return $aUpdatePersonFields; |
||
459 | } |
||
460 | |||
461 | /** |
||
462 | * Load an image from an URL. |
||
463 | * The method uses curl to be independet of [allow_url_fopen] enabled on the system. |
||
464 | * @param string $strURL |
||
465 | * @return string base64 encoded imagedata |
||
466 | */ |
||
467 | private function loadImageFromURL(string $strURL) : string |
||
502 | } |
||
503 | |||
504 | /** |
||
505 | * Load an image from an file. |
||
506 | * In most cases an uploaded imagefile. |
||
507 | * @param string $strFilename |
||
508 | * @return string base64 encoded imagedata |
||
509 | */ |
||
510 | private function loadImageFromFile(string $strFilename) : string |
||
529 | } |
||
530 | } |
||
531 |