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



Tutorial ini adalah bagian dari seri Membangun Startup Anda dengan PHP pada Envato Tuts+. Dalam seri ini, saya memandu Anda melalui peluncuran startup dari konsep ke realitas menggunakan aplikasi Meeting Planner saya sebagai contoh kehidupan nyata. Setiap langkah di sepanjang jalan, saya akan merilis kode Perencana Pertemuan sebagai contoh open-source yang dapat Anda pelajari. Saya juga akan membahas masalah bisnis terkait startup saat muncul.
Pengenalan
Selamat pagi. Hari ini, saya akan memandu Anda melalui cara saya menggunakan Google API untuk mengimpor kontak orang ke Perencana Pertemuan. Tujuannya adalah untuk membuatnya lebih cepat bagi orang-orang untuk mengundang teman-teman mereka ke pertemuan.
Jika Anda belum mencoba menjadwalkan pertemuan dengan Perencana Pertemuan, silahkan mencoba. Jika Anda menggunakan akun Google Anda untuk mendaftar, Anda dapat mengunjungi halaman Teman di atas dan Impor Kontak Google Anda. Bagikan pendapat dan masukan Anda di komentar di bawah ini.
Saya berpartisipasi dalam diskusi, tetapi Anda juga dapat menghubungi saya @reifman di Twitter (baru-baru ini akun saya diverifikasi jadi saya harus sekeren Justin Beeber (catatan untuk dewa editorial—saya yakin dengan ejaan ini. Saya pikir itu benar. Biarkan saja.) Saya selalu terbuka untuk ide-ide fitur baru untuk Meeting Planner serta saran untuk episode seri mendatang.
Sebagai pengingat, semua kode untuk Perencana Pertemuan disediakan sebagai sumber terbuka dan ditulis dalam Kerangka Yii2 untuk PHP. Jika Anda ingin mempelajari lebih lanjut tentang Yii2, periksa seri paralel saya Pemrogramman dengan Yii2.
Berpikir Tentang Integrasi Kontak Google
Halaman Teman
Banyak orang memiliki ribuan kontak di akun Google merek—dan hanya sedikit yang penting bagi mereka. Tetapi, untuk sebagian besar, tidak ada cara untuk membedakan mana yang dan mana yang tidak.
Saya percaya bahwa ukuran tabel Pengguna di Perencana Rapat memengaruhi kinerja keseluruhan layanan. Saya tidak ingin mengimpor kontak yang mungkin tidak relevan dengan tabel Pengguna.
Ini menciptakan beberapa komplikasi baik di UX dan kode di mana orang mencari dan mengakses teman-teman mereka di layanan.
Saya memutuskan pada akhirnya untuk membuat tabel terpisah untuk kontak dan untuk benar-benar menunjukkan ini secara terpisah di antarmuka pengguna untuk saat ini.



Memilih Peserta untuk Rapat
Di mana semua ini mengarah adalah bahwa itu akan menjadi lebih mudah bagi orang-orang untuk menambah teman dari kontak mereka dengan hanya mengetik beberapa karakter pertama. Saya menggunakan widget Typeahead di pop-up Add Participants yang ditampilkan di bawah ini:



Setelah saya mengimpor Kontak Google saya, mereka terintegrasi dengan teman-teman saya (orang yang sudah saya undang ke pertemuan atau diundang oleh).
Dalam hal ini, saya mulai mengetik sar dan sejumlah besar Sar- presfix names muncul:



Menemukan siapa pun yang diundang ke pertemuan dari Google Kontak Anda menjadi sangat cepat dan mudah (sampai Anda menambahkan banyak dari mereka, yang saya sebutkan lebih jauh di bawah).
Masalah Privasi
Saya juga tidak ingin menyalahgunakan kepercayaan orang lain dengan menyalahgunakan ribuan Kontak mereka. Pada saat ini, kami bahkan tidak akan menawarkan orang kesempatan untuk mengundang semua kontak Google mereka ke Perencana Pertemuan, meskipun kami mungkin menawarkan ini di masa mendatang. Kami tentu tidak akan mengirim email massal tanpa izin.
Menulis Kode
Jika Anda belum, intip Membangun Startup Anda Dengan PHP: Menyederhanakan Onramp Dengan OAuth. Itulah episode di mana saya pertama kali mengautentikasi API Google untuk Masuk dan Mendaftar OAuth.
Google berhati-hati tentang keamanan dengan API mereka. Mengingat apa yang baru-baru ini terjadi dengan peretasan Yahoo, saya menghargai ini lebih dalam. Namun, ini membuat API mereka lebih sulit daripada yang lain untuk diautentikasi dan dikerjakan.
Bahkan, saya menemukan Google Kontak API mengalir beberapa yang paling membingungkan, membuat frustrasi dan sulit yang harus saya tulis. Dan programmer PHP yang tidak disukai Google API—kami yang terakhir mendapatkan kode sampel.
Mari kita selami.
Membuat Tabel Alamat
Karena sudah ada tabel UserContact untuk telepon pengguna dan alamat Skype, saya memutuskan untuk memanggil Alamat tabel. Berikut migrasi untuk membuatnya:
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 |
}
|
Tentu saja, saya menggunakan Gii Yi untuk membantu saya dengan controller, model, dan pandangan. Ini telah dibahas sebelumnya di seri startup.
Memperluas Autentikasi Google API
Anda mungkin ingat halaman Google Credentials di tutorial yang disebutkan di atas:



Anda dapat menemukannya dari Konsol Pengembang Google.
Anda harus menambahkan URL untuk semua lingkungan Anda—pengembangan, pementasan, produksi, dll—dan untuk setiap pengontrol dan metode. Ini mempersulit upaya untuk bekerja dengan API Kontak, tetapi mungkin juga lebih baik mengamankan data orang.



Mengimpor Kontak Google
Inilah Google Kontak API v3.0. Saya tidak memperhatikan ketika saya mulai menulis kode yang sekarang mereka sarankan People API untuk akses read-only. Sayangnya, kode saya menggunakan API baca/tulis. Oke, jadi saya bukan jenius. Pengusaha jarang—bahkan Bill Gates mengatakan dia hanya beruntung.
Secara umum, saya menemukan Google Kontak API menjadi salah satu API yang lebih membingungkan dan sulit yang pernah saya gunakan.
Jika saya memiliki tingkat keahlian yang lebih tinggi di pengembangan Google API atau menghabiskan lebih banyak waktu untuk mengerjakan ini, saya mungkin telah menemukan pendekatan yang lebih sederhana. Tapi, dari apa yang bisa saya katakan, penting bahwa Anda melakukan semuanya dengan API dari satu URL. Dalam kasus saya https://meetingplanner.io/address/import.
Dan Google mengembalikan kunci dan mengalihkan Anda kembali ke URL ini berulang kali—jadi Anda perlu memperhatikan keadaan API dan mengatasi hal ini.
Saya menganggap ini semua dilakukan untuk meningkatkan keamanan, tetapi itu membutuhkan manajemen negara yang dibangun untuk apa yang seharusnya menjadi permintaan API sederhana. Manajemen negara dapat menghemat waktu, tetapi hanya jika dokumentasi dan kode contoh bagus. Dalam hal ini, untuk PHP, tidak.
Persiapan
Mari kita lihat 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 |
}
|
Di atas, saya mengawasi apakah Google ingin mengatur ulang token API-nya. Dalam hal ini, saya menghapus cookie yang telah saya simpan dan kembali ke metode untuk memulai kembali.
Membuat Permintaan Token
Di bawah ini, saya membuat permintaan pertama saya ke Google melalui pustaka klien PHP-nya:
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(); |
Perpustakaan PHP Google dalam versi beta. Sebenarnya, PHP pada umumnya merupakan pemikiran yang terpikirkan oleh Google. Jadi tidak selalu mudah bekerja di PHP dengan API mereka.
Perhatikan di atas bahwa $redirect_uri
Google adalah metode yang sama lagi: 'adress/import'
.
Selanjutnya, kami mencoba menempatkan token dari parameter kueri dalam 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 |
}
|
Jika Anda mendapatkan kode dari Google, Anda harus mengaturnya di cookie dan mengarahkan ulang ke halaman itu lagi. Itu aneh bagiku.
Dengan senang hati, saya juga menemukan bahwa jika saya tidak membuat loop kembali ketika kode itu hilang—untuk kembali ke halaman yang sama lagi, itu tidak akan bekerja secara konsisten.
Kemudian, kami membuat dan meminta Google untuk menyajikan dialog izin kepada pengguna untuk memberikan akses Perencana Pertemuan ke kontak mereka:
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 |
}
|



Saya harus menambahkan banyak kesalahan manajemen untuk mencari tahu mengapa itu tidak berhasil dan untuk mendapatkan semua ini untuk bekerja secara konsisten.
Anda menebaknya, jika ada kondisi kesalahan, saya sering mengarahkan ke metode pengontrol yang sama ini.
Memproses Data yang Dikembalikan
Ketika semuanya bekerja, menyenangkan, kode mudah mendapat untuk memproses data. Saat ini, kami mengambil 1.000 entri lima kali, berulang kali menyusun permintaan paginasi yang, tentu saja, dikirim kembali ke URL ini:
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); |
Menerjemahkan Google's XML (yang juga kompleks, dengan nama variabel aneh untuk pengembang PHP, misalnya kunci seperti $contact['gd$email'][0]['address']
dengan tanda dolar di tengah.
Di bawah ini, kami membuat setiap permintaan, menjalankan melalui data JSON, dan mengambil nama kontak untuk ditambahkan ke tabel Alamat:
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 |
}
|
Bekerja dengan Google Kontak API sangat sulit, tidak terdokumentasi dengan baik, dan membuang banyak waktu untuk saya. Meskipun saya telah bekerja dengan banyak API dengan sukses, saya tahu saya bukan ahli dalam hal ini. Karena masalah keamanan, saya tidak benar-benar ingin memukul Google karena hal ini—dalam banyak kasus ini, mereka mungkin tahu mengapa mereka melakukan hal-hal tertentu.
Tapi tidak apa-apa untuk sedikit bersenang-senang, kan?
Pertama, semua orang di Google adalah seorang jenius, dan mereka membuktikannya lagi dengan memperoleh API.ai, layanan brilian yang menghubungkan tombol Mendaftar ke Formulir Login-nya. Sungguh, mereka melakukan:
Saya yakin tim due diligence Google melihat kejeniusan di dalamnya yang tetap berada di luar manusia seperti saya. Mereka pasti berkata, "Wow, para programmer API.ai jenius seperti tim AdSense dan DFP kami! Mari tambahkan mereka ke Alphabet!"
Karena saya mungkin berbicara dengan investor angel dan VC tentang Meeting Planner di masa depan, saya ingin menjadi rendah hati. Tetapi saya akan merasa ngeri jika halaman rumah saya melakukan ini dan salah satu calon investor saya memperhatikan.
Alphabet baru (perusahaan induk Google yang baru) sangat memaafkan.
Memperluas Formulir Tambah Peserta
Akhirnya, mari kita lihat kode di belakang Formulir Tambahkan Peserta diperpanjang. Pada dasarnya, saya mengambil email dari tabel Teman dan kemudian email dari tabel Alamat:
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>
|
Namun, nilai opsi hanyalah email karena akan menjadi lebih rumit untuk menentukan teman seperti apa ini (dari Teman atau dari tabel Alamat).
Saya tidak super bangga dengan kode dan pendekatan di atas, tetapi terburu-buru menuju rilis beta, saya membuat kompromi untuk menyelesaikannya.
Dengan 5,000 Google Kontak di tarik turun teman saya, kinerjanya lebih lambat. Saya mungkin harus lebih baik menghubungkan kontrol ke pencarian database AJAX segera.
Dan saya menyia-nyiakan banyak waktu sejak awal untuk mencoba memperluas tabel Teman untuk menyertakan orang-orang yang saya undang serta Google Kontak. Namun, ini berubah menjadi kekacauan query database terkait yang sulit dikelola. Relasi tabel Pengguna dari tabel Teman mulai terputus untuk baris Kontak di mana mereka tidak valid, dan ini sebenarnya ternyata sangat sulit untuk diselesaikan. Mengelola penghapusan kunci asing yang ada naik dan turun melalui migrasi juga berbahaya.
Menutup Pikiran
Fitur-fitur ini merupakan contoh hebat dari tantangan mengandalkan API dengan dokumentasi yang buruk dalam bahasa yang Anda pilih dan membuat kompromi dalam arsitektur kode untuk saat itu untuk meluncurkan fitur ke jadwal rilis (memilih untuk tidak memotongnya).
Dan tentu saja masih ada masalah dengan peserta menambahkan kinerja dan halaman Teman UX yang perlu diperbaiki.
Sejujurnya, ruang lingkup Perencana Pertemuan telah mencapai titik di mana melakukan hal ini sebagai satu orang itu menakutkan. Dan akan sangat membantu jika memiliki lebih banyak sumber daya (misalnya. Anggota tim.)
Akhirnya, jika Anda belum, jadwalkan pertemuan pertama Anda dengan Perencana Pertemuan sekarang! Biar saya tahu apa yang Anda pikirkan di komentar di bawah ini. Anda juga dapat menghubungi saya @reifman. Saya selalu terbuka untuk ide-ide fitur baru dan saran topik untuk tutorial selanjutnya.
Sebuah tutorial tentang crowdfunding juga sedang dikerjakan, jadi silakan ikuti halaman Wefunder Meeting Planner kami.
Ikuti terus semua ini dan lebih banyak lagi tutorial yang akan datang dengan melihat seri Membangun Startup Anda Dengan PHP.