fernleafsystems/apiwrappers-freeagent 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

fernleafsystems/apiwrappers-freeagent

Composer 安装命令:

composer require fernleafsystems/apiwrappers-freeagent

包简介

PHP Wrapper For The FreeAgent API

README 文档

README

A PHP Wrapper for the FreeAgent accounting APIs.

Current version 0.2.x is beta. Please include using strict Composer versions.

Installation

To get started, add the package to your project by issuing the following command:

composer require fernleafsystems/apiwrappers-freeagent

Current version 0.2.x is beta. Please include using strict Composer versions.

Getting Started

This package has 2 parts:

  1. OAuth2 authorization
  2. FreeAgent API

The OAuth2 is mainly delivered as a Provider of the 'The League of Extraordinary Packages' OAuth2 implementation. It uses their latest version and make this package compatible with only PHP 5.6+

1) OAuth2 Authorization

OAuth2 is a big enough topic that it's beyond the scope of this readme, but the basics on how to achieve it with this package are as follows.

Stage 1 - "Pre-Auth": Build and Redirect User to Authorization URL

	$OauthProvider = ( new Connection() )
    		->setClientId( $freeagentAppClientId )
    		->setIsSandbox( $isSandbox )
    		->getOAuthProvider();
    		
	$redirectUrlBase = $OauthProvider->getBaseAuthorizationUrl();
	
	$authUrlParams = array(
    	'redirect_uri'  => 'https://www.theUrlForMyApp.com/To/Receive/Redirection/From/OAuth',
    	'client_id'     => $freeagentAppClientId,
    	'response_type' => 'code'
	);
    
	$authorizationUrl = $redirectUrlBase.'?'.http_build_query( $authUrlParams );
	
	header( 'Location: '. $authorizationUrl );

Assuming this is built correctly, the user will authorize your integration and be redirected back to your app based on the redirect_uri parameter.

At this point your App must capture the OAuth code that will be appended to the redirect, with the GET key code. You then need to store this somewhere in your app.

Stage 2 - "Pre-Token": Request an Access Token

To perform actions against the API you send an Access Token - not the OAuth code you obtained in the previous stage. To get this, your app will send a request for a new token and it will look like this:

	$tokenRequestParameters = array(
    	'code'          => $OauthCodeFromThePreviousStage,
    	'client_id'     => $freeagentAppClientId,
    	'client_secret' => $freeagentAppClientSecret,
    	'redirect_uri'  => 'https://www.theUrlForMyApp.com/To/Receive/Redirection/From/OAuth'
	);
	
	$OauthProvider = ( new Connection() )
    		->setClientId( $freeagentAppClientId )
    		->setIsSandbox( $isSandbox )
    		->getOAuthProvider();
	
	$accessToken = $OauthProvider->getAccessToken( 'authorization_code', $aTokenRequestParameters );
	
	// Store the Access Token $accessToken. 

Several critical points to note here.

The redirect_uri parameter is not strictly speaking used in this request. But YOU MUST INCLUDE IT. FreeAgent will cry very long and hard if the redirect_uri value used in this stage does not equal exactly the same value used in the previous stage. Read this again - it must be included and be EXACTLY the same.

The Access Token $accessToken is a \League\OAuth2\Client\Token\AccessToken and has all the accessor methods you need. You should store the access token value for future use, its expiration time, and also the refresh token value - you'll use this after the token has expired to request a new access token

Stage 3 - "Expired-Token": Request a new Access Token

	$tokenRequestParameters = array(
    	'client_id'     => $freeagentAppClientId,
    	'client_secret' => $freeagentAppClientSecret,
    	'refresh_token' => $OAuth2RefreshTokenStoredFromPreviousStage
	);
	
	$OauthProvider = ( new Connection() )
    		->setClientId( $freeagentAppClientId )
    		->setIsSandbox( $isSandbox )
    		->getOAuthProvider();
	
	$accessToken = $OauthProvider->getAccessToken( 'refresh_token', $tokenRequestParameters );
	
	// Store the Access Token $accessToken. 

2) Querying the FreeAgent API

Now that you have your AccessToken from Stage 3 above, you can make requests to the FreeAgent API with ease. Here's an example on how to request your Company information.

Example: Simple Data Retrieval

	$connection = ( new Connection() )
    	->setIsSandbox( $isSandbox )
    	->setAccessToken( $accessTokenFromStage3 );
	
	$oCompany = ( new \FernleafSystems\ApiWrappers\Freeagent\Entities\Company\Retrieve() )
	    ->setConnection( $connection )
	    ->retrieve();

In this example we simply create a Connection object and populate it with the Access Token, then feed this into the API entity Class that we want to use. In this case it's a retrieval for the Company Entity. This will then return a CompanyVO, which has lots of useful accessor methods on there for you to grab the particular piece of information you like, eg.:

	$oCompany = ( new \FernleafSystems\ApiWrappers\Freeagent\Entities\Company\Retrieve() )
	    ->setConnection( $connection )
	    ->retrieve();
	    
	echo $oCompany->getName();

This will print, for example, Acme Inc. if your company is so-called.

All the API classes are built like this and to perform any operations you just need to feed it the Connection object, and the system will do the rest.

Note: If you don't set the Sandbox flag, it'll assume it's not in the sandbox so you can exclude this flag if you're operating on live.

Example: Retrieving a Contact by its known FreeAgent ID

	$oContact = ( new \FernleafSystems\ApiWrappers\Freeagent\Entities\Contacts\Retrieve() )
	    ->setConnection( $connection )
	    ->setEntityId( 12345 )
	    ->retrieve();

The setEntityId() is the primary method for retrieving and updating entities. You must know the ID before you can attempt to retrieve it or update it, of course.

If you don't provide the ID, the system will throw an Exception before making the request.

The object returned, in this particular case, will be a ContactVO. It's similar to the CompanyVO provided in the previous example and has helpful accessor methods so you don't need to look up details from the API docs.

Taking the ContactVO object you've just retrieved, you could create a new Bill for it...

Example: Creating a new Bill

	$connection = ( new Connection() )->setAccessToken( $accessTokenFromStage3 );
	
	$oNewBill = ( new \FernleafSystems\ApiWrappers\Freeagent\Entities\Bills\Create() )
	    ->setConnection( $connection )
	    ->setContact( $freeagentContact ) // a Freeagent ContactVO
	    ->setReference( 'My Bill Reference' )
	    ->setTotalValue( '12.34' )  // a float
	    ->setCategory( $freeagentContegory ) // a Freeagent CategoryVO
	    ->setDatedOn( '2018-12-18' ) // or unix timestamp
	    ->setDueOn( '2018-12-25' )  // or unix timestamp
	    ->create();

This is the bare minimum you need to create a Bill. If any of these items are not present the API will throw an Exception before even making a request.

You can find out what's critical by looking at the method getCriticalRequestItems() on each API entity.

What if you want to find something, because you don't know the entity ID?

Search/Finding entities based on certain attributes

The FreeAgent API is delightfully inconsistent and makes search/filtering quite tricky. I'm guessing this is just down to how they've structured their data and their internal functions and they've mapped these internal inconsistencies out to their API. No matter, we've provided a way to filter and search.

Depending on what you're filtering and searching for, we can use the built-in filters that the API has provided, and failing that, we request in bulk and search through the results until we find any that match. It's greater load on their API, but we have no choice.

Here are some search examples.

Find a Bill with a given reference

	$oFoundBill = ( new \FernleafSystems\ApiWrappers\Freeagent\Entities\Bills\Find() )
	    ->setConnection( $connection )
	    ->filterByReference( 'My Bill Reference' )
	    ->first();

Using first() you assume that you have only 1 bill with the given reference. The problem with this example is that you haven't set a boundary for when this bill might have been created, so this could be quite a big API operation while it finds your bill in the haystack.

Instead, you could limit it by date, and even use a range with a radius.

Find a Bill with a given reference inside a date range

	$oFoundBill = ( new \FernleafSystems\ApiWrappers\Freeagent\Entities\Bills\Find() )
	    ->setConnection( $connection )
	    ->filterByDateRange( 123123123, 5 )
	    ->filterByReference( 'My Bill Reference' )
	    ->first();

This now limits the searching for this bill to within a specific date range. The 1st parameter must be a UNIX timestamp, and the 2nd parameter is the number of days radius outside of the timestamp, so in this case, 5 days before, and 5 days after - a time window of 10 days total.

Note: If no bill is found to match this criteria, null is returned.

In this example we're looking for 1 bill with the given reference. But what if we wanted all the bills within that time period.

Find all Bills within a given date range

	$aAllBills = ( new \FernleafSystems\ApiWrappers\Freeagent\Entities\Bills\Find() )
	    ->setConnection( $connection )
	    ->filterByDateRange( 123123123, 5 )
	    ->all();

We've removed the Reference filter and now calling using all(). This will return an array of BillVO objects. It may be empty, or not, but it will be an array().

The Rest...

Each entity type has its own set of methods, parameters, and filter options. You have to look at each to determine what your options are.

The examples above are specific to those particular entities. There is of course some overlap, but all entities follow the documentation and specification of the FreeAgent API.

Errors and Exceptions

When retrieving, creating and updating, there are some basic checks on the data to ensure the absolute minimums are provided before a request is sent out. If these checks fail an Exception is thrown.

If these checks don't fail, and the request fails, you have a few options. This will likely change in the future as the current implementation is flawed and not satisfactory.

As it currently stands you can do the following on all API Entities

	$oCreator = new \FernleafSystems\ApiWrappers\Freeagent\Entities\Invoices\Create();
    /** creation code parameters*/
	$oNewInvoice = $oCreator->create();
	
	if ( is_null( $oNewInvoice ) ) {
	    $sErrorMessageFromFreeAgent = $oCreator->getLastError()->getMessage();
	}

If you find bugs, suggestions for improvements etc., please do let us know.

fernleafsystems/apiwrappers-freeagent 适用场景与选型建议

fernleafsystems/apiwrappers-freeagent 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 5.35k 次下载、GitHub Stars 达 7, 最近一次更新时间为 2017 年 10 月 23 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「Api Wrapper」 「freeagent」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 fernleafsystems/apiwrappers-freeagent 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 fernleafsystems/apiwrappers-freeagent 我们能提供哪些服务?
定制开发 / 二次开发

基于 fernleafsystems/apiwrappers-freeagent 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 5.35k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 7
  • 点击次数: 2
  • 依赖项目数: 1
  • 推荐数: 0

GitHub 信息

  • Stars: 7
  • Watchers: 3
  • Forks: 1
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2017-10-23