本教程是 Envato Tuts +上的“ 使用PHP构建启动”系列 的一部分。 在本系列文章中,我 将以 我的 Meeting Planner 应用程序作为真实示例 ,指导您完成从概念到现实的启动 。 在此过程的每一步中,我都会将Meeting Planner代码作为开放源代码示例发布,您可以从中学习。 我还将解决与启动相关的业务问题。
介绍
早上好。 今天,我将向您介绍如何使用Google API将人们的联系人导入Meeting Planner。 目的是使人们更快地邀请朋友参加会议。
如果您尚未尝试使用Meeting Planner安排会议 ,请尝试一下。 如果您使用Google帐户进行注册,则可以访问上方的“朋友”页面 ,然后导入您的Google联系人 。 在下面的评论中分享您的想法和反馈。
我确实参与了讨论,但是您也可以在Twitter上通过@reifman与我联系 (最近,我的帐户已通过验证,因此我必须像贾斯汀·比伯特(Justin Beebert)一样酷(注:社论之神,我对这种拼写很有信心。我认为这是正确的。放开它。)我始终对Meeting Planner的新功能想法以及未来系列剧集的建议持开放态度。
提醒一下,Meeting Planner的所有代码均以开源形式提供,并以Yii2 Framework for PHP编写。 如果您想了解有关Yii2的更多信息,请查看我的平行系列“ 使用Yii2编程” 。
考虑Google通讯录集成
朋友页面
许多人的Google帐户中有成千上万的联系人,而对他们来说很重要。 但是,对于大多数人来说,没有办法辨别哪些是,哪些不是。
我相信Meeting Planner中User表的大小会影响服务的整体性能。 我不想将可能永远不相关的联系人导入User表。
这在用户和服务中寻找和访问他们的朋友的UX和代码中造成了一些麻烦。
我最终决定为联系人创建一个单独的表,并暂时在用户界面中单独显示该表。
选择参加会议的人
所有这些潜在客户的好处是,人们只需键入前几个字符,便可以更轻松地从联系人中添加朋友。 我在如下所示的“ 添加参与者”弹出窗口中使用Typeahead小部件:
导入Google联系人后,它们会与我的朋友(我已经邀请参加会议或受其邀请的人)整合在一起。
在这种情况下,我开始键入sar,然后出现一堆Sar-前缀名称 :
从您的Google通讯录中找到任何人邀请参加会议变得非常容易(除非您添加了很多人,我将在下面进一步提及)。
隐私问题
我也不想滥用人们的数千个联系人来滥用他们的信任。 目前,我们甚至不会为人们提供邀请所有Google联系人加入Meeting Planner的机会,尽管我们将来可能会提供。 未经许可,我们当然不会批量向他们发送电子邮件。
编写代码
如果还没有,请使用PHP构建启动:使用OAuth简化Onramp 。 这是我首先认证用于OAuth登录和注册的Google API的一集。
Google对其API的安全性非常谨慎。 鉴于Yahoo黑客最近发生的事情,我对此深表感谢。 但是,这使得它们的API比其他API更加难以验证和使用。
实际上,我发现Google Contacts API流程有些我不得不编写的最令人困惑,沮丧和困难的流程。 而且Google API不利于PHP程序员-我们是最后一个获得示例代码的人。
让我们潜入。
创建地址表
由于已经存在用于用户电话和Skype地址的UserContact表,因此我决定调用该表Address。 这是创建它的迁移:
<?php
use yii\db\Schema;
use yii\db\Migration;
class m160904_001244_create_table_address extends Migration
{
public function up()
{
$tableOptions = null;
if ($this->db->driverName === 'mysql') {
$tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
}
$this->createTable('{{%address}}', [
'id' => Schema::TYPE_PK,
'user_id' => Schema::TYPE_BIGINT.' NOT NULL',
'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0',
'firstname' =>Schema::TYPE_STRING.' NOT NULL',
'lastname' =>Schema::TYPE_STRING.' NOT NULL',
'fullname' =>Schema::TYPE_STRING.' NOT NULL',
'email' =>Schema::TYPE_STRING.' NOT NULL',
'created_at' => Schema::TYPE_INTEGER . ' NOT NULL',
'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL',
], $tableOptions);
$this->addForeignKey('fk_address_user_id', '{{%address}}', 'user_id', '{{%user}}', 'id', 'CASCADE', 'CASCADE');
}
public function down()
{
$this->dropForeignKey('fk_address_user_id', '{{%address}}');
$this->dropTable('{{%address}}');
}
}
当然,我使用Yii's Gii来帮助我处理控制器,模型和视图。 在启动系列的前面已经介绍了这一点。
扩展Google API身份验证
您可能还记得上面提到的教程中的Google凭据页面:
您可以从Google Developers Console中找到它。
您必须为所有环境(开发,登台,生产等)以及每个控制器和方法添加URL。 这使使用其Contacts API的工作复杂化,但也可能会更好地保护人们的数据。
导入Google联系人
这是Google Contacts API v3.0 。 当我开始编写代码时,我没有注意到他们现在建议使用People API进行只读访问。 不幸的是,我的代码正在使用读/写API。 好吧,所以我不是天才。 企业家很少 -连比尔·盖茨都说他很幸运。
总的来说,我发现Google Contacts API是我使用过的更令人困惑和困难的API之一。
如果我在Google API开发方面具有更高的专业知识水平,或者花了更多时间从事此工作,那么我可能会找到一种更简单的方法。 但是,据我所知,从一个URL使用API进行所有操作非常重要。 就我而言, https://meetingplanner.io/address/import 。
Google会返回密钥并反复将您重定向到该URL,因此您需要注意API的状态并解决此问题。
我想这一切都是为了提高安全性,但它要求将状态管理内置到简单的API请求中。 状态管理可以节省时间,但前提是文档和示例代码都不错。 在这种情况下,对于PHP,不是。
入门
让我们看一下AddressController.php actionImport()
:
public function actionImport() {
// imports contacts from the google api
$address = new Address();
// create session cookies
$session = Yii::$app->session;
// if we request code reset, then remove the google_code cookie
// i.e. if google_code expires
if (isset($_GET['reset']) && !$session->has('google_code_reset')) {
// prevent loops
$session->set('google_code_reset');
// reset the google_code
$session->remove('google_code');
$this->redirect(['import']);
}
在上面,我正在观察Google是否要重置其API令牌。 在这种情况下,我将删除存储在其中的Cookie,然后重定向回该方法以重新开始。
提出令牌请求
下面,我通过其PHP客户端库向Google发出第一个请求:
// always remove the reset request cookie
$session->remove('google_code_reset');
// build the API request
$redirect_uri=Url::home(true).'address/import';
$session->open();
$client = new \Google_Client();
$client -> setApplicationName('Meeting Planner');
$client -> setClientid( Yii::$app->components['authClientCollection']['clients']['google']['clientId']);
$client -> setClientSecret(Yii::$app->components['authClientCollection']['clients']['google']['clientSecret']);
$client -> setRedirectUri($redirect_uri);
$client -> setAccessType('online');
$client -> setScopes('https://www.google.com/m8/feeds');
$googleImportUrl = $client -> createAuthUrl();
Google PHP库处于beta版。 实际上,PHP通常是Google的事后思考。 因此,在PHP中使用其API并非总是那么容易。
请注意,上面的Google的$redirect_uri
也是相同的方法: 'address/import'
。
接下来,我们尝试将查询参数中的令牌放置在cookie中:
// moves returned code to session variables and returns here
if (isset($_GET['code']))
{
$auth_code = $_GET['code'];
$session->set('google_code',$auth_code);
header('Location: '.Url::home(true).'address/import');
// do not remove - breaks the API
exit; // do not replace with yii app end
// do not remove above exit
} else {
$session_code = $session->get('google_code');
if (!isset($session_code)) {
$this->redirect( $googleImportUrl);
}
}
如果确实从Google获得了代码,则必须在Cookie中进行设置,然后再次重定向到页面。 这对我来说很奇怪。
令人高兴的是,我还发现,如果我在丢失代码时没有创建循环,而要再次返回同一页面,它将无法始终如一地工作。
然后,我们构造并要求Google向用户显示一个权限对话框,以使Meeting Planner可以访问其联系人:
if (isset($session_code)) {
$auth_code = $session_code;
$fields=array(
'code'=> urlencode($auth_code),
'client_id'=> urlencode(Yii::$app->components['authClientCollection']['clients']['google']['clientId']),
'client_secret'=> urlencode(Yii::$app->components['authClientCollection']['clients']['google']['clientSecret']),
'redirect_uri'=> urlencode($redirect_uri),
'grant_type'=> urlencode('authorization_code'),
);
// Requests the access token
$post = '';
foreach($fields as $key=>$value)
{
$post .= $key.'='.$value.'&';
}
$post = rtrim($post,'&');
$result = $address->curl('https://accounts.google.com/o/oauth2/token',$post);
$response = json_decode($result);
if (isset($response->error)) {
if ($response->error_description == 'Code was already redeemed.') {
$session->remove('google_code');
return $this->redirect(['import']);
}
if ($response->error_description == 'Invalid code.') {
$session->remove('google_code');
return $this->redirect(['import']);
}
var_dump($response);
echo Yii::t('frontend','There was an error. Please contact support.');
}
if (isset($response->access_token) || empty($response->access_token)) {
$accesstoken = $response->access_token;
} else {
echo Yii::t('frontend','There was an error. No access token. Please contact support.');
}
我必须添加许多错误管理来弄清楚为什么它不起作用,并使所有这些一致地起作用。
您猜对了,如果有错误情况,我经常会重定向到同一控制器方法。
处理返回的数据
当一切正常时,有趣,简单的代码即可处理数据。 当前,我们五次获取1,000个条目,重复构造分页请求,这些请求当然会发送回此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);
转换Google的XML(这也很复杂,PHP开发人员使用奇怪的变量名,例如$contact['gd$email'][0]['address']
,中间带有一个美元符号)。
下面,我们通过JSON数据发出每个请求,并获取联系人姓名以添加到Address表中:
// Requests the data
$startIndex = 1;
$request_data = true;
$max_results = Address::CONTACTS_PAGE_SIZE;
$numberPages = 0;
while ($request_data && $numberPages <5) {
//echo 'calling with startIndex: '.$startIndex.'<br />';
$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['feed']['entry'])) {
//var_dump ($url);
//var_dump ($xmlresponse);
exit;
}
$resultsCount =count($contacts['feed']['entry']);
//echo 'count: '.$resultsCount.'<br />';
//var_dump (count($contacts['feed']['entry']));
// process out contacts without email addresses
//$return = array();
if ($resultsCount>0) {
foreach($contacts['feed']['entry'] as $contact) {
if (isset($contact['gd$email'])) {
$temp = array (
'firstname' => (isset($contact['gd$name']['gd$givenName']['$t'])?$contact['gd$name']['gd$givenName']['$t']:''),
'lastname' => (isset($contact['gd$name']['gd$familyName']['$t'])?$contact['gd$name']['gd$familyName']['$t']:''),
'fullname'=> $contact['title']['$t'],
'email' => $contact['gd$email'][0]['address'],
);
//$return[]=$temp;
$address->add($temp);
} else {
continue;
}
}
if ($resultsCount<$max_results) {
Yii::$app->getSession()->setFlash('success', Yii::t('backend','Your contacts have been imported.'));
return $this->redirect(['/friend','tab'=>'address']);
}
}
//var_dump($return);
$numberPages++;
$startIndex+=$max_results;
}
使用Google Contacts API非常困难,没有足够的文档记录,这对我来说浪费了很多时间。 虽然我已经成功使用了许多API,但是我知道我不是专家。 出于安全方面的考虑,我真的不想在这方面受到Google的殴打-在许多情况下,他们可能知道为什么要以某些方式做事。
但是可以戳一点乐趣,对吗?
首先, Google的每个人都是天才 ,他们通过收购API.ai再次证明了这一点,API.ai是一项出色的服务,将其“注册”按钮链接到其“登录表单”。 确实,他们做到了:
我确信Google的尽职调查团队看到了天才,这一天才仍然超越了像我这样的凡人。 他们必须说:“哇,API.ai程序员是我们的AdSense和DFP团队一样的天才!让我们将它们添加到Alphabet中!”
由于我将来可能会与天使和风险投资人谈论Meeting Planner,因此我要谦虚。 但是,如果我的主页做到了这一点,而我的一位潜在投资者注意到了,我会感到震惊。
新的Alphabet(Google的新母公司)是如此宽容。
扩展“添加参与者”表单
最后,让我们看一下扩展的“添加参与者”表单后面的代码。 基本上,我从“朋友”表中获取电子邮件,然后从“地址”表中获取电子邮件:
$friendsEmail=[];
$friendsId=[];
$fq = Friend::find()->where(['user_id'=>Yii::$app->user->getId()])->all();
// to do - add a display name field for right side of input
$fa = Address::find()
->select(['id','email'])
->where(['user_id'=>Yii::$app->user->getId()])
->limit(5000)
->all();
foreach ($fq as $f) {
$friendsEmail[]=$f->friend->email; // get constructed name fields
$friendsId[]=$f->id;
}
foreach ($fa as $f) {
$friendsEmail[]=$f->email; // get constructed name fields
$friendsId[]=$f->id;
}
if (count($friendsEmail)>0) {
?>
<p><strong>Choose From Your Friends</strong></p>
<select class="combobox input-large form-control" id="participant-email" name="Participant[email]">
<option value="" selected="selected"><?= Yii::t('frontend','type or click to choose friends') // chg meetingjs if change string ?></option>
<?php
foreach ($friendsEmail as $email) {
?>
<option value="<?= $email;?>"><?= $email;?></option>
<?php
}
?>
<?php
}
?>
</select>
但是,选项值只是电子邮件,因为指定它是哪种类型的朋友(从“朋友”或“地址”表)会变得更加复杂。
我并不为上面的代码和方法感到骄傲,但是在急于发布beta版本时,我做出了让步,以完成它。
我的朋友中有5,000个Google通讯录,因此性能降低。 我可能需要尽快将控件更好地链接到AJAX数据库搜索。
而且,我在尝试扩展“朋友表”以包括我邀请的人以及Google通讯录上浪费了很多时间。 但是,这变成了一堆难以管理的相关数据库查询。 “朋友”表中的“用户”表关系开始中断,其中“联系人”行将为空,这实际上很难解决。 管理通过迁移上下移动现有外键的操作也很麻烦。
总结思想
这些功能很好地说明了依赖API以及所选语言的不良文档并在代码体系结构方面做出折衷以将功能按发布时间表发布(选择不削减功能)的挑战。
当然,参与者添加性能和“好友”页面UX仍然存在一些问题需要解决。
坦白说,会议策划人的范围已经达到了一个人要完成这项任务的地步。 拥有更多资源(例如团队成员)将很有帮助。
最后,如果您还没有, 请立即与Meeting Planner安排您的第一次会议! 让我知道您在以下评论中的想法。 您也可以与我联系@reifman 。 我总是乐于接受新功能的想法和主题建议,以供将来的教程使用。
有关众筹的教程也正在编写中 ,因此请关注我们的WeFunder Meeting Planner页面 。
请阅读“ 用PHP构建您的启动”系列,继续关注所有这些以及以后的教程。