globalvisionmedia / oauth2-harvest
为PHP League OAuth2-Client提供的Harvest OAuth 2.0客户端 - 更新了Nile Suan的Harvest API v2提供者
1.2
2023-02-10 08:59 UTC
Requires
- php: >=5.5.0
- league/oauth2-client: ^2.0
README
对PHP League的OAuth 2.0客户端提供Harvest OAuth 2.0支持,感谢Nile Suan提供的原始harvest提供者。此包更新以支持Harvest API的V2版本
安装
composer require globalvisionmedia/oauth2-harvest
获取Harvest访问密钥
-
要获取密钥,您需要在Harvest开发者区域创建OAuth2应用程序:(https://id.getharvest.com/developers)
-
单击创建新的Auth2应用程序按钮来创建密钥
-
为您的应用程序选择一个名称
-
重定向URL必须与下面的redirectUri完全相同(包括http://或https://),并且是您的应用程序的URL。访问 - 选择我需要访问一个账户 产品 - 选择我想要访问Harvest。
-
记下客户端ID和客户端密钥
-
您需要在Harvest上知道您的账户ID。创建OAuth2应用程序时不会显示它,但如果您创建一个临时的“个人访问令牌”,则会显示您的账户ID。记下来,然后您可以删除临时的个人访问令牌。
使用方法
使用方法与The League的OAuth客户端相同,使用\GlobalVisionMedia\OAuth2\MYOBClient\Provider\MYOB作为提供者,除了以下内容
-
Harvest要求您在Guzzle中设置一个用户代理(见以下示例)
-
您需要向Harvest API提供账户ID(见上文)
-
Harvest的API是受限制的 - 文档中规定的限制为每100秒15次调用(最简单的选项是每秒限制为6次) - 请见以下示例
实例化
$provider = new \GlobalVisionMedia\OAuth2\HarvestClient\Provider\Harvest([
'clientId' => 'yourId', // The Client ID assigned to you by Harvest
'clientSecret' => 'yourSecret', // The Client Secret assigned to you by Harvest
'redirectUri' => 'yourRedirectUri', // The Redirect URL you specified for your app on Harvest
'accountId' => 'yourAccountId, // See point 6 above
'userAgent' => 'My User Agent' // This can be any string but ideally should identify your application
]);
提示(也适用于其他提供者)
When you instantiate your provider, you can also pass a second parameter containing a collaborator for your httpClient.
Doing that means you can define your own Guzzle client and do things such as:
1. Setting Guzzle into debug mode, or
2. Adding a rate limiter mildeware (composer require spatie/guzzle-rate-limiter-middleware)
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use Spatie\GuzzleRateLimiterMiddleware\RateLimiterMiddleware;
define('CALLBACK_URI','https://xxx.yyy/zzzzzzzz.php');
define('HARVEST_CLIENT_ID','xxxxxxxxxxxxxxxxxxxxxxxx');
define('HARVEST_CLIENT_SECRET','xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
define('HARVEST_USER_AGENT','My Harvest Integration');
define('HARVEST_ACCOUNT_ID','xxxxxx');
// Add a rate limiter
$stack=HandlerStack::create();
$stack->push(RateLimiterMiddleware::perSecond(6));
$options=['debug' => $debug, 'exceptions' => false, 'handler' => $stack];
$httpClient = new Client($options);
$this->provider = \GlobalVisionMedia\OAuth2\HarvestClient\Provider\Harvest([
'redirectUri' => CALLBACK_URI,
'clientId' => HARVEST_CLIENT_ID,
'clientSecret' => HARVEST_CLIENT_SECRET,
'accountId' => HARVEST_ACCOUNT_ID,
'userAgent' => HARVEST_USER_AGENT
],
['httpClient' => $httpClient]);
示例应用程序
<?php
require __DIR__ . '/vendor/autoload.php';
// This is a prebuilt rate limiter for guzzle - unfortunately MYOB does not seem to work as documented any you may need to add additional sleep() calls.
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use Spatie\GuzzleRateLimiterMiddleware\RateLimiterMiddleware;
define('CALLBACK_URI','https://xxx.yyy/zzzzzzzz.php');
define('HARVEST_CLIENT_ID','xxxxxxxxxxxxxxxxxxxxxxxx');
define('HARVEST_CLIENT_SECRET','xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
define('HARVEST_USER_AGENT','My Harvest Integration');
define('HARVEST_ACCOUNT_ID','xxxxxx');
define('CACHEDIR','/tmp/'); // a writeable area for storing tokens
class myHarvest {
public function __construct($debug=false) {
$this->cache=CACHEDIR.'_API_TOKEN_CACHE_'.md5(__FILE__.get_class($this));
// Add the rate limiter
$stack=HandlerStack::create();
$stack->push(RateLimiterMiddleware::perSecond(6));
$options=['debug' => $debug, 'exceptions' => false, 'handler' => $stack];
$httpClient = new Client($options);
$this->provider = \GlobalVisionMedia\OAuth2\HarvestClient\Provider\Harvest([
'redirectUri' => CALLBACK_URI,
'clientId' => HARVEST_CLIENT_ID,
'clientSecret' => HARVEST_CLIENT_SECRET,
'username' => HARVEST_ACCOUNT_ID,
'password' => HARVEST_USER_AGENT
],
['httpClient' => $httpClient]);
// First check our cache to see if we have an existing token. This sppeds the application by avoiding the need to re-authenticate.
if (file_exists($this->cache)) {
$this->accessToken=unserialize(file_get_contents($this->cache));
if ($this->accessToken->hasExpired()) {
$this->accessToken=$this->provider->getAccessToken('refresh_token', ['refresh_token'=>8]);
}
} elseif (!isset($_GET['code'])) {
// If we don't have an authorization code then get one
$authUrl = $this->provider->getAuthorizationUrl();
$_SESSION['oauth2state'] = $this->provider->getState();
header('Location: '.$authUrl);
exit;
// Check given state against previously stored one to mitigate CSRF attack
} elseif (empty($_GET['state']) ||
(isset($_SESSION['oauth2state']) && $_GET['state'] !== $_SESSION['oauth2state'])) {
if (isset($_SESSION['oauth2state'])) unset($_SESSION['oauth2state']);
exit('Invalid state');
// Try to get an access token using the authorisation code grant.
} else try {
$this->accessToken = $this->provider->getAccessToken('authorization_code', [ 'code' => $_GET['code'] ]);
// Cache the token
file_put_contents($this->cache,serialize($this->accessToken));
} catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {
// Failed to get the access token or user details.
exit($e->getMessage());
}
}
public function getItemsAsArray($result) {
$keys=array_keys($result);
//find whichever key is not in the following list - it must be our data.
$dataKey=array_diff($keys,['page','total_pages','total_entries','next_page','previous_page','links']);
return $result[$dataKey[0]];
}
public function apiCall($method, $url, $pageSize=100) {
if (strpos($url,"https://")===false) { // is this a nextpage link? if so leave url unchanged
$url="https://api.harvestapp.com/v2$url?per_page=$pageSize";
}
$request=$this->provider->AuthenticatedRequest($method, $url, $this->accessToken);
return $this->provider->getParsedResponse($request);
}
// This function retrieves paginated data as a single array
public function fetchAll($method, $url, $pageSize=100) {
$allResults=array();
do {
$result=$this->apiCall($method,$url,$pageSize);
$allResults=array_merge($allResults,$this->getItemsAsArray($result));
$url = $result['links']['next'];
} while (!empty($url));
return $allResults;
}
}
session_start();
$myHarvest=new myHarvest();
print_r($myHarvest->fetchAll('GET', '/contacts'));