Advertisement
  1. Code
  2. Yii

Construyendo su inicio: Importación con la API de contactos de Google

Scroll to top
Read Time: 14 min
This post is part of a series called Building Your Startup With PHP.
Building Your Startup: Completing Group Scheduling
Building Your Startup: Error Logging

() translation by (you can also view the original English article)

Final product imageFinal product imageFinal product image
What You'll Be Creating

Este tutorial forma parte de la serie Construyendo su startup con PHP en Envato Tuts+. En esta serie, te guío a través del lanzamiento de una startup desde el concepto a la realidad utilizando mi aplicación Meeting Planner como un ejemplo de la vida real. En cada paso del camino, lanzaré el código de Meeting Planner como ejemplos de código abierto de los que puede aprender. También abordaré los problemas comerciales relacionados con el startup a medida que surjan.

Introducción

Buenos días. Hoy, le explicaré cómo utilicé Google API para importar contactos de personas en Meeting Planner. El objetivo es hacer que sea más rápido para las personas invitar a sus amigos a las reuniones.

Si no ha intentado programar una reunión con Meeting Planner, pruébelo. Si usa su cuenta de Google para registrarse, puede visitar la página de amigos que se encuentra arriba e Importar sus contactos de Google. Comparte tus pensamientos y comentarios en los comentarios a continuación.

Participo en las discusiones, pero también puedes contactarme @reifman en Twitter (recientemente mi cuenta fue verificada, así que debo ser tan genial como Justin Beebert (nota para los dioses editoriales.Tengo confianza en esta ortografía. Creo que es correcta Déjalo.) Siempre estoy abierto a nuevas ideas de funciones para Meeting Planner, así como sugerencias para futuros episodios de series.

Como recordatorio, todo el código para Meeting Planner se proporciona como código abierto y está escrito en Yii2 Framework para PHP. Si desea obtener más información acerca de Yii2, consulte mis series paralelas Programando con Yii2.

Pensando en la integración de contactos de Google

La página de amigos

Muchas personas tienen miles de contactos en su cuenta de Google, y pocos son importantes para ellos. Pero, para la mayoría, no hay forma de discernir cuáles son y cuáles no.

Creo que el tamaño de la tabla de usuarios en Meeting Planner afecta el rendimiento general del servicio. No quería importar contactos que quizás nunca sean relevantes en la tabla User.

Esto creó algunas complicaciones tanto en UX como en el código donde las personas buscan y acceden a sus amigos en el servicio.

Al final, decidí crear una tabla separada para contactos y mostrarla por separado en la interfaz de usuario por el momento.

Building Startups Google Contacts API - List of Imported Contacts on Friends PageBuilding Startups Google Contacts API - List of Imported Contacts on Friends PageBuilding Startups Google Contacts API - List of Imported Contacts on Friends Page

Elegir participantes para las reuniones

Donde todo esto conduce es que será más fácil para las personas agregar amigos de sus contactos simplemente escribiendo los primeros caracteres. Estoy usando un widget de Typeahead en la ventana emergente Agregar participantes que se muestra a continuación:

Building Startups Google Contacts API - Add a Participant FormBuilding Startups Google Contacts API - Add a Participant FormBuilding Startups Google Contacts API - Add a Participant Form

Después de haber importado mis contactos de Google, se integran con mis amigos (personas a las que ya he invitado a reuniones o que me han invitado).

En este caso, comencé a escribir sar y aparecen un montón de nombres de prefijo Sar:

Building Startups Google Contacts API - Dropdown from Friend and Address TableBuilding Startups Google Contacts API - Dropdown from Friend and Address TableBuilding Startups Google Contacts API - Dropdown from Friend and Address Table

Encontrar a alguien para invitar a una reunión desde sus Contactos de Google se vuelve bastante rápido y fácil (hasta que agregue muchos de ellos, que menciono más adelante).

Problemas de privacidad

Tampoco quiero abusar de los fideicomisos de las personas mediante el uso indebido de sus miles de contactos. En este momento, ni siquiera ofreceremos a las personas la oportunidad de invitar a todos sus contactos de Google a Meeting Planner, aunque podríamos ofrecer esto en el futuro. Ciertamente no enviaremos correos electrónicos masivos sin permiso.

Escribir el código

Si aún no lo has hecho, echa un vistazo a Construyendo su startup con PHP: Simplificando Onramp con OAuth. Ese es el episodio en el que primero autenticé las API de Google para el inicio de sesión y registro de OAuth.

Google tiene cuidado con la seguridad con sus API. A la luz de lo que sucedió recientemente con los hacks de Yahoo, aprecio esto más profundamente. Sin embargo, esto hace que sus API sean más difíciles que otras para autenticarse y trabajar.

De hecho, encontré que la API de contactos de Google fluía de la manera más confusa, frustrante y difícil que he tenido que escribir. Y la API de Google desaprueba a los programadores de PHP: somos los últimos en obtener un código de muestra.

Vamos a sumergirnos

Creando la tabla de direcciones

Dado que ya existe una tabla UserContact para el teléfono del usuario y las direcciones de Skype, decidí llamar a la dirección de la tabla. Aquí está la migración para crearlo:

1
<?php
2
use yii\db\Schema;
3
use yii\db\Migration;
4
5
class m160904_001244_create_table_address extends Migration
6
{
7
    public function up()
8
    {
9
        $tableOptions = null;
10
        if ($this->db->driverName === 'mysql') {
11
            $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
12
        }
13
14
        $this->createTable('{{%address}}', [
15
            'id' => Schema::TYPE_PK,
16
            'user_id' => Schema::TYPE_BIGINT.' NOT NULL',
17
            'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0',
18
            'firstname' =>Schema::TYPE_STRING.' NOT NULL',
19
            'lastname' =>Schema::TYPE_STRING.' NOT NULL',
20
            'fullname' =>Schema::TYPE_STRING.' NOT NULL',
21
            'email' =>Schema::TYPE_STRING.' NOT NULL',
22
            'created_at' => Schema::TYPE_INTEGER . ' NOT NULL',
23
            'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL',
24
        ], $tableOptions);
25
        $this->addForeignKey('fk_address_user_id', '{{%address}}', 'user_id', '{{%user}}', 'id', 'CASCADE', 'CASCADE');
26
    }
27
28
    public function down()
29
    {
30
      $this->dropForeignKey('fk_address_user_id', '{{%address}}');
31
      $this->dropTable('{{%address}}');
32
    }
33
  }

Por supuesto, utilicé el Gii de Yii para ayudarme con el controlador, el modelo y las vistas. Esto se ha cubierto anteriormente en la serie de startup.

Aumento de la autenticación API de Google

Puede recordar la página Credenciales de Google en el tutorial mencionado anteriormente:

Building Startups Google Contacts API - Google API Credentials FormBuilding Startups Google Contacts API - Google API Credentials FormBuilding Startups Google Contacts API - Google API Credentials Form

Puede encontrarlo desde Google Developers Console.

Debe agregar direcciones URL para todos sus entornos (desarrollo, puesta en escena, producción, etc.) y para cada controlador y método. Esto complica los esfuerzos para trabajar con su API de Contactos, pero probablemente también protege mejor los datos de las personas.

Building Startups Google Contacts API - Google API Credentials Form lower partBuilding Startups Google Contacts API - Google API Credentials Form lower partBuilding Startups Google Contacts API - Google API Credentials Form lower part

Importación de contactos de Google

Aquí está la API de Contactos de Google v3.0. No me di cuenta cuando comencé a escribir el código que ahora recomiendan People API para el acceso de solo lectura. Desafortunadamente, mi código está usando la API de lectura / escritura. De acuerdo, entonces no soy un genio. Los empresarios raramente lo son, incluso Bill Gates dice que tuvo suerte.

En general, encontré que la API de Contactos de Google es una de las API más confusas y difíciles que he usado.

Si tuviera un mayor nivel de experiencia en el desarrollo de API de Google o si hubiera dedicado más tiempo a trabajar en esto, podría haber encontrado un enfoque más simple. Pero, por lo que pude ver, era importante que hicieras todo con la API desde una URL. En mi caso, https://meetingplanner.io/address/import.

Y Google devuelve las claves y le redirige a esta URL repetidamente, por lo que debe estar atento al estado de la API y trabajar en torno a esto.

Supongo que todo esto se hace para aumentar la seguridad, pero requiere una gestión de estado integrada en lo que de otro modo serían simples solicitudes API. La administración del estado puede ahorrar tiempo, pero solo si la documentación y el código de muestra son buenos. En este caso, para PHP, no lo son.

Empezando

Veamos AddressController.php actionImport():

1
public function actionImport() {
2
      // imports contacts from the google api

3
      $address = new Address();
4
      // create session cookies

5
      $session = Yii::$app->session;
6
      // if we request code reset, then remove the google_code cookie

7
      // i.e. if google_code expires

8
      if (isset($_GET['reset']) && !$session->has('google_code_reset')) {
9
        // prevent loops

10
        $session->set('google_code_reset');
11
        // reset the google_code

12
        $session->remove('google_code');
13
        $this->redirect(['import']);
14
      }

Importando Google Above, estoy viendo si Google quiere restablecer sus tokens de API. En este caso, eliminé las cookies en las que las había almacenado y volví a dirigirme al método para comenzar de nuevo.

Hacer la solicitud de un token

A continuación, hago mi primera solicitud a Google a través de su biblioteca cliente PHP:

1
// always remove the reset request cookie

2
      $session->remove('google_code_reset');
3
      // build the API request

4
      $redirect_uri=Url::home(true).'address/import';
5
      $session->open();
6
      $client = new \Google_Client();
7
      $client -> setApplicationName('Meeting Planner');
8
      $client -> setClientid( Yii::$app->components['authClientCollection']['clients']['google']['clientId']);
9
      $client -> setClientSecret(Yii::$app->components['authClientCollection']['clients']['google']['clientSecret']);
10
      $client -> setRedirectUri($redirect_uri);
11
      $client -> setAccessType('online');
12
      $client -> setScopes('https://www.google.com/m8/feeds');
13
      $googleImportUrl = $client -> createAuthUrl();

La biblioteca de Google PHP está en beta. De hecho, PHP generalmente es una idea de último momento para Google. Por lo tanto, no siempre es fácil trabajar en PHP con sus API.

Observe arriba que $redirect_uri de Google es el mismo método otra vez: 'address/import'.

A continuación, tratamos de colocar el token del parámetro de consulta en una cookie:

1
// moves returned code to session variables and returns here

2
      if (isset($_GET['code']))
3
        {
4
      	$auth_code = $_GET['code'];
5
        $session->set('google_code',$auth_code);
6
    		header('Location: '.Url::home(true).'address/import');
7
        // do not remove - breaks the API

8
        exit; // do not replace with yii app end

9
        // do not remove above exit

10
    	} else {
11
        $session_code = $session->get('google_code');
12
        if (!isset($session_code)) {
13
            $this->redirect( $googleImportUrl);
14
        }
15
      }

Si obtiene el código de Google, debe configurarlo en la cookie y redirigirlo a la página nuevamente. Eso es extraño para mí.

Alegremente, también encontré que si no creaba un bucle cuando faltaba el código, para volver a la misma página otra vez, no funcionaría de manera consistente.

Luego, construimos y le pedimos a Google que presente al usuario un diálogo de permiso para dar acceso a Meeting Planner a sus contactos:

1
if (isset($session_code)) {
2
        $auth_code = $session_code;
3
          $fields=array(
4
	        'code'=>  urlencode($auth_code),
5
	        'client_id'=>  urlencode(Yii::$app->components['authClientCollection']['clients']['google']['clientId']),
6
	        'client_secret'=>  urlencode(Yii::$app->components['authClientCollection']['clients']['google']['clientSecret']),
7
	        'redirect_uri'=>  urlencode($redirect_uri),
8
	        'grant_type'=>  urlencode('authorization_code'),
9
	    );
10
      // Requests the access token

11
	    $post = '';
12
	    foreach($fields as $key=>$value)
13
	    {
14
	        $post .= $key.'='.$value.'&';
15
	    }
16
	    $post = rtrim($post,'&');
17
	    $result = $address->curl('https://accounts.google.com/o/oauth2/token',$post);
18
	    $response =  json_decode($result);
19
      if (isset($response->error)) {
20
          if ($response->error_description == 'Code was already redeemed.') {
21
            $session->remove('google_code');
22
            return $this->redirect(['import']);
23
          }
24
          if ($response->error_description == 'Invalid code.') {
25
            $session->remove('google_code');
26
            return $this->redirect(['import']);
27
          }
28
          var_dump($response);
29
          echo Yii::t('frontend','There was an error. Please contact support.');
30
      }
31
      if (isset($response->access_token) || empty($response->access_token)) {
32
          $accesstoken = $response->access_token;
33
      } else {
34
        echo Yii::t('frontend','There was an error. No access token. Please contact support.');
35
      }
Building Startups Google Contacts API - Google Asks User for PermissionsBuilding Startups Google Contacts API - Google Asks User for PermissionsBuilding Startups Google Contacts API - Google Asks User for Permissions

Tuve que agregar mucha administración de errores para descubrir por qué no funcionaba y lograr que todo esto funcionara de manera consistente.

Lo adivinaste, si hay una condición de error, a menudo me redirecciona a este mismo método de controlador.

Procesamiento de datos devueltos

Cuando todo funciona, el código fácil y divertido procesa los datos. Actualmente, tomamos 1,000 entradas cinco veces, construyendo repetidamente solicitudes de paginación que, por supuesto, se envían a esta URL:

1
$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;
2
$xmlresponse =  $address->curl($url);

Traducir el XML de Google (que también es complejo, con nombres de variables impares para desarrolladores de PHP, por ejemplo, claves como $contact['gd$email'][0]['address'] con un signo de dólar en el medio.

A continuación, hacemos cada solicitud, ejecutando los datos JSON y tomando los nombres de los contactos para agregarlos a la tabla de direcciones:

1
// Requests the data

2
      $startIndex = 1;
3
      $request_data = true;
4
      $max_results = Address::CONTACTS_PAGE_SIZE;
5
      $numberPages = 0;
6
      while ($request_data && $numberPages <5) {
7
         //echo 'calling with startIndex: '.$startIndex.'<br />';

8
         $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;
9
         $xmlresponse =  $address->curl($url);
10
         $contacts = json_decode($xmlresponse,true);
11
         if (!isset($contacts['feed']['entry'])) {
12
           //var_dump ($url);

13
           //var_dump ($xmlresponse);

14
           exit;
15
         }
16
        $resultsCount =count($contacts['feed']['entry']);
17
        //echo 'count: '.$resultsCount.'<br />';

18
         //var_dump (count($contacts['feed']['entry']));

19
         // process out contacts without email addresses

20
         //$return = array();

21
         if ($resultsCount>0) {
22
           foreach($contacts['feed']['entry'] as $contact) {
23
             if (isset($contact['gd$email'])) {
24
               $temp = array (
25
                 'firstname' => (isset($contact['gd$name']['gd$givenName']['$t'])?$contact['gd$name']['gd$givenName']['$t']:''),
26
                 'lastname' => (isset($contact['gd$name']['gd$familyName']['$t'])?$contact['gd$name']['gd$familyName']['$t']:''),
27
                 'fullname'=> $contact['title']['$t'],
28
                 'email' => $contact['gd$email'][0]['address'],
29
               );
30
               //$return[]=$temp;

31
               $address->add($temp);
32
             } else {
33
               continue;
34
             }
35
           }
36
           if ($resultsCount<$max_results) {
37
             Yii::$app->getSession()->setFlash('success', Yii::t('backend','Your contacts have been imported.'));
38
             return $this->redirect(['/friend','tab'=>'address']);
39
           }
40
         }
41
         //var_dump($return);

42
         $numberPages++;
43
         $startIndex+=$max_results;
44
       }

Trabajar con la API de Contactos de Google fue muy difícil, no estaba bien documentado, y desperdiciaba mucho tiempo para mí. Si bien he trabajado con muchas API con éxito, sé que no soy un experto en esto. Debido a problemas de seguridad, realmente no quiero golpear a Google por esto; en muchos de estos casos, probablemente saben por qué lo hacen de ciertas maneras.

Pero está bien burlarse un poco, ¿verdad?

En primer lugar, todo el mundo en Google es un genio, y lo probaron de nuevo adquiriendo API.ai, un servicio brillante que vincula su botón de registro con su formulario de inicio de sesión. Realmente, lo hicieron:

Estoy seguro de que el equipo de diligencia debida de Google vio en él un genio que permanece más allá de los mortales como yo. Deben haber dicho: "¡Guau, los programadores de API.ai son genios como nuestros equipos de AdSense y DFP! ¡Vamos a agregarlos al alfabeto!"

Como puedo estar hablando con inversores angélicos y de VC sobre Meeting Planner en el futuro, quiero ser humilde. Pero me horrorizaría si mi página de inicio hiciera esto y uno de mis posibles inversores lo notara.

El nuevo Alphabet (la nueva compañía matriz de Google) es tan indulgente.

Extender el formulario Agregar Participantes

Finalmente, echemos un vistazo al código detrás del Formulario de Agregar Participantes extendido. Básicamente, estoy tomando los correos electrónicos de la tabla de amigos y luego los correos electrónicos de la tabla de direcciones:

1
$friendsEmail=[];
2
    $friendsId=[];
3
    $fq = Friend::find()->where(['user_id'=>Yii::$app->user->getId()])->all();
4
    // to do - add a display name field for right side of input

5
    $fa = Address::find()
6
      ->select(['id','email'])
7
      ->where(['user_id'=>Yii::$app->user->getId()])
8
      ->limit(5000)
9
      ->all();
10
    foreach ($fq as $f) {
11
      $friendsEmail[]=$f->friend->email; // get constructed name fields

12
      $friendsId[]=$f->id;
13
    }
14
    foreach ($fa as $f) {
15
      $friendsEmail[]=$f->email; // get constructed name fields

16
      $friendsId[]=$f->id;
17
    }
18
    if (count($friendsEmail)>0) {
19
      ?>
20
      <p><strong>Choose From Your Friends</strong></p>
21
      <select class="combobox input-large form-control" id="participant-email" name="Participant[email]">
22
      <option value="" selected="selected"><?= Yii::t('frontend','type or click to choose friends') // chg meetingjs if change string ?></option>

23
      <?php
24
      foreach ($friendsEmail as $email) {
25
      ?>
26
        <option value="<?= $email;?>"><?= $email;?></option>
27
      <?php
28
        }
29
      ?>
30
      <?php
31
    }
32
    ?>
33
    </select>

Sin embargo, el valor de la opción es solo el correo electrónico, ya que se hubiera vuelto más complicado designar qué tipo de amigo es este (de la tabla Amigo o de la Dirección).

No estoy muy orgulloso del código y el enfoque anterior, pero corriendo hacia la versión beta, hice concesiones para hacerlo.

Con 5.000 contactos de Google en el menú desplegable de mis amigos, el rendimiento es más lento. Probablemente necesite vincular mejor el control a una búsqueda de base de datos AJAX pronto.

Y perdí mucho tiempo al principio al tratar de ampliar la tabla de Amigos para incluir a las personas a las que invité, así como a los Contactos de Google. Sin embargo, esto se convirtió en un lío de consultas de bases de datos relacionadas difíciles de gestionar. Las relaciones de la tabla de usuario de la tabla Amigos comenzaron a fallar para las filas de Contactos donde serían nulas, y esto realmente resultó ser muy difícil de resolver. Gestionar la eliminación de las claves externas existentes hacia arriba y hacia abajo a través de migraciones también es traicionero.

Pensamientos finales

Estas características fueron excelentes ejemplos de los desafíos de confiar en las API con documentación deficiente en el idioma elegido y de hacer concesiones en la arquitectura del código por el momento para lanzar las características a un cronograma de lanzamiento (optando por no cortarlas).

Y ciertamente todavía hay problemas con el desempeño de agregar participante y la UX de la página de amigos que deben corregirse.

Honestamente, el alcance de Meeting Planner ha llegado al punto en que hacerlo como una persona es desalentador. Y sería útil tener más recursos (es decir, miembros del equipo).

Finalmente, si aún no lo ha hecho, programe su primera reunión con Meeting Planner ahora mismo. Déjame saber lo que piensas en los comentarios a continuación. También puede comunicarse conmigo @reifman. Siempre estoy abierto a nuevas ideas de características y sugerencias de temas para futuros tutoriales.

También se está preparando un tutorial sobre crowdfunding, así que por favor siga nuestra página WeFunder Meeting Planner.

Estén atentos para todo esto y más próximos tutoriales visitando la serie Construyendo su startup con PHP.

Enlaces relacionados

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.