Construire votre démarrage Importer avec l'API Google Contacts

Ce que vous allez créer

Ce tutoriel fait partie de la Construire votre démarrage avec la série PHP sur Envato Tuts +. Dans cette série, je vous guide dans le lancement d’une startup du concept à la réalité en utilisant mes Planificateur de réunion application comme exemple de la vie réelle. À chaque étape du processus, je publierai le code de Meeting Planner sous forme d’exemples open source à partir desquels vous pourrez apprendre. Je traiterai également les problèmes liés au démarrage au fur et à mesure qu'ils surviennent.

introduction

Bonjour. Aujourd'hui, je vais vous expliquer comment j'ai utilisé l'API Google pour importer les contacts des personnes dans Meeting Planner. L’objectif est de permettre aux gens d’inviter leurs amis à des réunions plus rapidement..

Si vous n'avez pas essayé de planifier une réunion avec Meeting Planner, essayez-le. Si vous utilisez votre compte Google pour vous inscrire, vous pouvez visiter la page Amis ci-dessus et Importez vos contacts Google. Partagez vos opinions et commentaires dans les commentaires ci-dessous. 

Je participe aux discussions, mais vous pouvez aussi me joindre à @reifman sur Twitter (mon compte a récemment été vérifié; je dois donc être aussi cool que Justin Beebert (note aux dieux de la rédaction: j'ai confiance en cette orthographe. Je pense que c'est exact Laissez-le.) Je suis toujours ouvert aux nouvelles idées de fonctionnalités de Meeting Planner ainsi qu’aux suggestions pour les futurs épisodes de la série..

Pour rappel, tout le code de Meeting Planner est fourni en source ouverte et écrit dans le framework Yii2 pour PHP. Si vous souhaitez en savoir plus sur Yii2, consultez mes séries parallèles Programmer avec Yii2. 

Penser à l'intégration de Google Contacts

La page des amis

De nombreuses personnes ont des milliers de contacts dans leur compte Google et peu d’entre elles sont importantes pour elles. Mais, pour la plupart, il n'y a aucun moyen de discerner qui sont et qui ne sont pas.

Je pense que la taille de la table des utilisateurs dans Meeting Planner influe sur les performances globales du service. Je ne voulais pas importer des contacts qui pourraient ne jamais être pertinents dans la table des utilisateurs.

Cela a créé des complications à la fois dans UX et dans le code où les gens recherchent et accèdent à leurs amis dans le service..

J'ai finalement décidé de créer un tableau séparé pour les contacts et de l'afficher séparément dans l'interface utilisateur pour l'instant..

Choix des participants pour les réunions

Tout cela conduit à dire qu'il sera plus facile pour les gens d'ajouter des amis à partir de leurs contacts en tapant simplement les premiers caractères. J'utilise un widget Typeahead dans le Ajouter des participants pop-up montré ci-dessous:

Après avoir importé mes contacts Google, ils sont intégrés à mes amis (personnes déjà invitées à des réunions ou invitées par)..

Dans ce cas, j'ai commencé à taper sar et tout un tas de Noms de préfixe Sar apparaître:

Il est très simple et rapide de trouver quelqu'un à inviter à une réunion à partir de vos contacts Google (jusqu'à ce que vous en ajoutiez beaucoup, ce que je mentionnerai ci-dessous)..

Problèmes de confidentialité

Je ne veux pas non plus abuser des fiducies des gens en abusant de leurs milliers de contacts. Pour le moment, nous n'offrirons même pas aux utilisateurs la possibilité d'inviter tous leurs contacts Google dans Meeting Planner, bien que nous puissions le proposer ultérieurement. Nous ne les enverrons certainement pas par courriel sans autorisation.

Écrire le code

Si vous ne l'avez pas encore fait, jetez un coup d'œil à Construire votre startup avec PHP: Simplifier Onramp avec OAuth. C'est l'épisode où j'ai authentifié pour la première fois les API Google pour la connexion et l'inscription à OAuth..

Google fait très attention à la sécurité avec ses API. À la lumière de ce qui est arrivé récemment avec les hacks de Yahoo, je l’apprécie plus profondément. Cependant, cela rend leurs API plus difficiles que d’autres à s’authentifier et à travailler avec.

En fait, j’ai constaté que l’API Google Contacts présentait certaines des opérations les plus déroutantes, frustrantes et difficiles que j’ai eu à écrire. Et les API de Google API défavorisent les programmeurs - nous sommes les derniers à obtenir un exemple de code.

Plongeons dedans.

Création de la table d'adresses

Puisqu'il existe déjà une table UserContact pour le téléphone de l'utilisateur et les adresses Skype, j'ai décidé d'appeler l'adresse de la table. Voici la migration pour le créer:

db-> nomDuPort === 'mysql') $ tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB';  $ this-> createTable ('% address', ['id' => Schema :: TYPE_PK, 'ID_utilisateur' => Schema :: TYPE_BIGINT. 'NOT NULL', 'status' => Schema :: TYPE_SMALLINT. 'NOT NULL DEFAULT 0', 'prénom' => Schéma :: TYPE_STRING. 'NOT NULL', 'nom de famille' => Schéma :: TYPE_STRING. 'NOT NULL', 'nom complet' => Schéma :: TYPE_STRING. ' NOT NULL ',' email '=> Schema :: TYPE_STRING.' NOT NULL ',' created_at '=> Schema :: TYPE_INTEGER.' NOT NULL ',' updated_at '=> Schéma :: TYPE_INTEGER.' NOT NULL ',] , $ tableOptions); $ this-> addForeignKey ('fk_address_user_id', '% address', 'id_utilisateur', '% user', 'id', 'CASCADE', 'CASCADE');  public function down () $ this-> dropForeignKey ('fk_address_user_id', '% address'); $ this-> dropTable ('% address');  

Bien sûr, j'ai utilisé Giii de Yii pour m'aider avec le contrôleur, le modèle et les vues. Cela a été couvert plus tôt dans la série de démarrage. 

Extension de l'authentification Google API

Vous vous souvenez peut-être de la page d'informations d'identification Google dans le didacticiel mentionné ci-dessus:

Vous pouvez le trouver à partir de la console des développeurs Google..

Vous devez ajouter des URL pour tous vos environnements (développement, transfert, production, etc.) et pour chaque contrôleur et méthode. Cela complique les efforts pour travailler avec son API de contacts, mais sécurise probablement mieux les données des utilisateurs..

Importer des contacts Google

Voici l'API Google Contacts v3.0. Lorsque j'ai commencé à écrire le code, je n'avais pas remarqué qu'ils recommandaient maintenant l'API People pour un accès en lecture seule. Malheureusement, mon code utilise l'API en lecture / écriture. Ok, alors je ne suis pas un génie. Les entrepreneurs sont rarement, même Bill Gates dit qu'il a eu de la chance. 

En général, j'ai trouvé que l'API Google Contacts était l'une des API les plus confuses et les plus difficiles que j'aie jamais utilisées..

Si j'avais un niveau d'expertise supérieur dans le développement d'API Google ou si je passais plus de temps à travailler dessus, j'aurais peut-être trouvé une approche plus simple. Mais, à ce que je sache, il était important de tout faire avec l'API à partir d'une URL. Dans mon cas, https://meetingplanner.io/address/import.

De plus, Google renvoie les clés et vous redirige de manière répétée vers cette URL. Vous devez donc surveiller l'état de l'API et contourner ce problème..

Je suppose que tout est fait pour renforcer la sécurité, mais cela nécessite une gestion des états intégrée à ce qui serait autrement des demandes d'API simples. La gestion des états permet de gagner du temps, mais uniquement si la documentation et l'exemple de code sont corrects. Dans ce cas, pour PHP, ils ne sont pas.

Commencer

Regardons AddressController.php actionImport ():

fonction publique actionImport () // importe les contacts de google api $ address = new Address (); // créer des cookies de session $ session = Yii :: $ app-> session; // si nous demandons la réinitialisation du code, supprimons le cookie google_code // si le code google expire si (isset ($ _GET ['reset']) &&! $ session-> a ('google_code_reset')) // des boucles empêchent $ session-> set ('google_code_reset'); // réinitialiser le google_code $ session-> remove ('google_code'); $ this-> redirect (['import']); 

Ci-dessus, je recherche si Google souhaite réinitialiser ses jetons API. Dans ce cas, je supprime les cookies dans lesquels je les ai stockés et je les redirige vers la méthode pour recommencer.

Faire la demande d'un jeton

Ci-dessous, je fais ma première demande à Google via sa bibliothèque cliente PHP:

// supprime toujours le cookie de demande de réinitialisation $ session-> remove ('google_code_reset'); // construit la demande d'API $ redirect_uri = Url :: home (true). 'address / import'; $ session-> open (); $ client = new \ Google_Client (); $ client -> setApplicationName ('Planificateur de réunion'); $ client -> setClientid (Yii :: $ app-> composants ['authClientCollection'] ['clients'] ['google'] ['clientId']); $ client -> setClientSecret (Yii :: $ app-> composants ['authClientCollection'] ['clients'] ['google'] ['clientSecret']); $ client -> setRedirectUri ($ redirect_uri); $ client -> setAccessType ('en ligne'); $ client -> setScopes ('https://www.google.com/m8/feeds'); $ googleImportUrl = $ client -> createAuthUrl ();

La bibliothèque Google PHP est en version bêta. En fait, PHP est généralement une réflexion après coup pour Google. Il n'est donc pas toujours facile de travailler en PHP avec leurs API.

Notez ci-dessus que Google $ redirect_uri est la même méthode à nouveau: 'adresse / importation'

Ensuite, nous essayons de placer le jeton du paramètre de requête dans un cookie:

// déplace le code renvoyé vers les variables de session et renvoie ici si (isset ($ _ GET ['code']))) $ code_auteur = $ _GET ['code']; $ session-> set ('google_code', $ auth_code); en-tête ('Location:' .Url :: home (true). 'adresse / importation'); // ne pas supprimer - rompt la sortie de l'API; // ne remplace pas par yii app end // ne supprime pas la sortie ci-dessus else $ session_code = $ session-> get ('google_code'); if (! isset ($ session_code)) $ this-> redirect ($ googleImportUrl); 

Si vous recevez le code de Google, vous devez le définir dans le cookie et le rediriger vers la page. C'est étrange pour moi.

Heureusement, j'ai également constaté que si je ne créais pas de boucle lorsque le code manquait - pour revenir à la même page, cela ne fonctionnerait pas systématiquement..

Ensuite, nous construisons et demandons à Google de présenter à l'utilisateur une boîte de dialogue d'autorisation permettant à Meeting Planner d'accéder à ses contacts:

if (isset ($ session_code)) $ auth_code = $ session_code; $ fields = array ('code' => urlencode ($ auth_code), 'client_id' => urlencode (Yii :: $ app-> composants ['authClientCollection'] ['clients'] ['google'] ['clientId' ]), 'client_secret' => urlencode (Yii :: $ app-> composants ['authClientCollection'] ['clients'] ['google'] ['clientSecret']), 'redirect_uri' => urlencode ($ redirect_uri) , 'grant_type' => urlencode ('code_autorisation'),); // Demande le jeton d'accès $ post = "; foreach (les champs $ sont sous la forme $ key => $ value) $ post. = $ Key. '='. $ Value. '&'; $ Post = rtrim ($ post , '&'); $ resultat = $ adresse-> curl ('https://accounts.google.com/o/oauth2/token' ,$post); $ response = json_decode ($ result); if (isset ( $ response-> error)) if ($ response-> error_description == 'Le code a déjà été utilisé.') $ session-> remove ('google_code'); renvoie $ this-> redirect (["import"]) ; if ($ response-> error_description == 'Code invalide.') $ session-> remove ('google_code'); retourne $ this-> redirect (['import']); var_dump ($ response); echo Yii :: t ('frontend', 'Il y a une erreur. Veuillez contacter le support.'); if (isset ($ response-> access_token) || empty ($ response-> access_token)) $ accesstoken = $ response-> access_token; else echo Yii :: t ('frontend', 'Une erreur s'est produite. Aucun jeton d'accès. Contactez le support technique.');

J'ai dû ajouter beaucoup de gestion des erreurs pour comprendre pourquoi cela ne fonctionnait pas et pour que tout cela fonctionne de manière cohérente..

Vous l'avez deviné, s'il y a une condition d'erreur, je redirigerais souvent vers cette même méthode de contrôleur.

Traitement des données renvoyées

Lorsque tout fonctionne, le code simple et amusant permet de traiter les données. Actuellement, nous récupérons cinq fois 1 000 entrées, en construisant à plusieurs reprises des demandes de pagination qui, bien sûr, sont renvoyées à cette URL:

$ url = 'https://www.google.com/m8/feeds/contacts/default/full?max-results='.$max_results.'&start-index='.$startIndex.'&alt=json&v=3.0&oauth_token = '. $ accesstoken; $ xmlresponse = $ address-> curl ($ url);

Traduire le code XML de Google (également complexe, avec des noms de variable impairs pour les développeurs PHP, par exemple des clés telles que $ contact ['gd $ email'] [0] ['adresse'] avec un signe dollar au milieu.

Ci-dessous, nous effectuons chaque demande, analysons les données JSON et récupérons les noms des contacts à ajouter à la table Address:

// Demande les données $ startIndex = 1; $ request_data = true; $ max_results = Address :: CONTACTS_PAGE_SIZE; $ numberPages = 0; while ($ request_data && $ numberPages <5)  //echo 'calling with startIndex: '.$startIndex.'
'; $ url = 'https://www.google.com/m8/feeds/contacts/default/full?max-results='.$max_results.'&start-index='.$startIndex.'&alt=json&v=3.0&oauth_token = '. $ accesstoken; $ xmlresponse = $ address-> curl ($ url); $ contacts = json_decode ($ xmlresponse, true); if (! isset ($ contacts ['flux'] ['entrée']))) // var_dump ($ url); // var_dump ($ xmlresponse); sortie; $ resultsCount = count ($ contacts ['feed'] ['entry']); // echo 'count:'. $ resultsCount. '
'; // var_dump (count ($ contacts ['feed'], ['entry']))); // traite les contacts sans adresses e-mail // $ return = array (); if ($ resultsCount> 0) foreach ($ contacts ['feed'] ['entry'] en tant que $ contact) if (isset ($ contact ['gd $ email']))) $ temp = array ('prénom '=> (isset ($ contact [' gd $ nom ']] [' gd $ nom donné '] [' $ t '])? $ contact [' gd $ nom '] [' gd $ nom donné '] [' $ t ']: "),' lastname '=> (isset ($ contact [' gd $ name '] [' gd $ nom de famille '] [' $ t ']))? $ contact [' gd $ name '] [' gd $ familyName '] [' $ t ']: "),' fullname '=> $ contact [' title '] [' $ t '],' email '=> $ contact [' gd $ email '] [0] ['adresse'], ); // $ return [] = $ temp; $ address-> add ($ temp); else continue; if ($ resultsCount<$max_results) Yii::$app->getSession () -> setFlash ('success', Yii :: t ('backend', 'Vos contacts ont été importés.')); return $ this-> redirect (['/ ami', 'tab' => 'adresse']); // var_dump ($ return); $ numberPages ++; $ startIndex + = $ max_results;

Travailler avec l'API Google Contacts était très difficile, pas bien documenté et m'a fait perdre beaucoup de temps. Bien que j'ai travaillé avec beaucoup d'API avec succès, je sais que je ne suis pas un expert dans ce domaine. Pour des raisons de sécurité, je ne veux pas vraiment critiquer Google à ce sujet. Dans beaucoup de cas, ils savent probablement pourquoi ils agissent de différentes manières..

Mais ça va de piquer un peu de plaisir, non?

Tout d’abord, tout le monde chez Google est un génie, et l’a encore prouvé en acquérant API.ai, un service génial qui relie son bouton d’enregistrement à son formulaire de connexion. Vraiment, ils ont fait:

Je suis sûr que l'équipe de Google chargée de la diligence raisonnable a constaté un génie qui reste au-delà des mortels comme moi. Ils ont dû dire: "Waouh, les programmeurs API.ai sont des génies comme nos équipes AdSense et DFP! Ajoutons-les à l'alphabet!"

Puisqu'il est possible que je parle de Meeting Planner à des investisseurs providentiels et de capital-risque à l'avenir, je veux être humble. Mais je serais horrifié si ma page d'accueil le faisait et l'un de mes investisseurs potentiels remarquait.

Le nouvel Alphabet (la nouvelle société mère de Google) pardonne tellement.

Extension du formulaire Ajouter des participants

Pour finir, examinons simplement le code derrière le formulaire Ajouter un participant étendu. Fondamentalement, je récupère des courriels de la table des amis, puis de la table des adresses:

$ friendsEmail = []; $ friendsId = []; $ fq = Friend :: find () -> where (['user_id' => Yii :: $ app-> user-> getId ()]) -> all (); // à faire - ajoute un champ de nom d'affichage pour le côté droit de l'entrée $ fa = Adresse :: find () -> select (['id', 'email']) -> where (['user_id' => Yii: : $ app-> user-> getId ()]) -> limit (5000) -> all (); foreach ($ fq as $ f) $ amisEmail [] = $ f-> ami-> email; // récupère les champs de noms construits $ friendsId [] = $ f-> id;  foreach ($ fa as $ f) $ friendsEmail [] = $ f-> email; // récupère les champs de noms construits $ friendsId [] = $ f-> id;  if (count ($ friendsEmail)> 0) ?> 

Choisissez parmi vos amis

Cependant, la valeur de l’option n’est que l’e-mail, car il serait devenu plus compliqué de désigner le type d’ami utilisé (à partir de la table Friend ou de la table Address)..

Je ne suis pas très fier du code et de l'approche ci-dessus, mais me précipitant vers la version bêta, j'ai fait des compromis pour y arriver..

Avec 5 000 contacts Google dans la liste déroulante de mes amis, les performances sont plus lentes. J'ai probablement besoin de mieux lier le contrôle à une recherche de base de données AJAX bientôt.

De plus, j’ai perdu beaucoup de temps au début à essayer d’étendre la table des amis aux personnes que j’avais invitées et à Google Contacts. Cependant, cela s'est transformé en un fouillis de requêtes de base de données connexes difficiles à gérer. Les relations de la table User de la table Friends ont commencé à se rompre pour les lignes Contacts où elles seraient nulles, ce qui s'est avéré très difficile à résoudre. La gestion de la suppression des clés étrangères existantes de haut en bas lors des migrations est également déloyale.

Pensées finales

Ces fonctionnalités sont d'excellents exemples des défis que représentent l'utilisation d'API avec une documentation médiocre dans la langue de votre choix et des compromis d'architecture de code pour le moment, afin de lancer des fonctionnalités selon un calendrier de publication (en choisissant de ne pas les couper)..

Et il reste certainement des problèmes avec le participant qui ajoute des performances et la page amis UX qui doivent être corrigés.. 

Honnêtement, la portée de Meeting Planner a atteint un point où il est difficile de le faire en une seule personne. Et il serait utile d’avoir plus de ressources (par exemple, les membres de l’équipe).

Enfin, si ce n'est déjà fait, rendez-vous dès maintenant pour votre première réunion avec Meeting Planner! Faites-moi savoir ce que vous pensez dans les commentaires ci-dessous. Vous pouvez également me contacter @reifman. Je suis toujours ouvert aux nouvelles idées de fonctionnalités et suggestions de sujets pour les prochains tutoriels.

Un tutoriel sur le financement participatif est également en préparation. Veuillez suivre notre page WeFunder Meeting Planner..

Restez à l'affût de tout cela et de plusieurs autres tutoriels à venir en consultant la série Construire son démarrage avec PHP. 

Liens connexes

  • Planificateur de réunion
  • Suivre le profil de financement de Meeting Planner
  • Programmation avec la série Yii2 (Envato Tuts +)
  • API Google Contacts v3.0
  • API de personnes