calcinai/xero-php
PHP client library for the Xero API with Guzzle-based requests and ORM-like models. Supports OAuth 2 authorization code flow, access/refresh tokens, and multi-tenant (organisation) access via tenantId. Install via Composer and query Xero resources via XeroPHP\Application.
Installation
composer require calcinai/xero-php:^2.8.0
Register the service provider in config/app.php (unchanged):
'providers' => [
Calcinai\Xero\XeroServiceProvider::class,
],
Configuration Publish the config file (unchanged):
php artisan vendor:publish --provider="Calcinai\Xero\XeroServiceProvider"
Update .env with your Xero credentials (unchanged):
XERO_CONSUMER_KEY=your_consumer_key
XERO_CONSUMER_SECRET=your_consumer_secret
XERO_CALLBACK_URL=http://your-app.dev/xero/callback
First OAuth Flow (unchanged)
use Calcinai\Xero\Facades\Xero;
$authUrl = Xero::getAuthorizationUrl();
return redirect()->to($authUrl);
Handle Callback (unchanged)
Route::get('/xero/callback', function () {
$code = request('code');
$token = Xero::getAccessToken($code);
});
First API Call with Enhanced Models Fetch contacts or payments with improved model support:
// Contacts (fixed boolean conversion issue)
$contacts = Xero::api('contacts')->get();
// Payments with new Details property
$payment = Xero::api('payments')->find($paymentId);
$details = $payment->Details; // New property
CRUD Operations with Enhanced Models
// Batch Payments with history support
$batchPayment = Xero::api('batchPayments')->create($data);
$history = $batchPayment->getHistory(); // New trait method
// Manual Journals with history support
$journal = Xero::api('manualJournals')->create($data);
$history = $journal->getHistory(); // New trait method
Search Queries (Fixed Issue)
Use %2b instead of + for search queries:
$contacts = Xero::api('contacts')->search('name', 'John%2bDoe');
Error Handling Improvements Access raw responses in exceptions:
try {
$contact = Xero::api('contacts')->find(9999);
} catch (\Calcinai\Xero\Exceptions\NotFoundException $e) {
$rawResponse = $e->getResponse(); // Now contains full response
}
Payment Details Access
$payment = Xero::api('payments')->find($paymentId);
$details = $payment->Details; // New property from v2.8.0
Type Safety with PHP 8.4 Updated to handle PHP 8.4's nullable parameter deprecation:
$contact = Xero::api('contacts')->find($id);
// No breaking changes, but improved type safety
Leverage New Model Features
// For BatchPayment/ManualJournal
$model = Xero::api('batchPayments')->find($id);
if (method_exists($model, 'getHistory')) {
$history = $model->getHistory();
}
Search Query Builder
$searchTerm = urlencode('John Doe');
$searchTerm = str_replace('+', '%2b', $searchTerm);
$results = Xero::api('contacts')->search('name', $searchTerm);
Exception Handling with Raw Responses
try {
$invoice = Xero::api('invoices')->find($id);
} catch (\Calcinai\Xero\Exceptions\BadRequestException $e) {
$raw = $e->getResponse();
// Handle specific validation errors
}
PHP 8.4 Compatibility Ensure your IDE shows proper type hints:
public function updateContact(XeroContact $contact): void {
// ...
}
Model Property Access
$payment = Xero::api('payments')->find($id);
// New property access
$payment->Details->Reference; // Example
Boolean Conversion Fix
$contact = Xero::api('contacts')->find($id);
// isActive will be boolean, not string
Search Query Encoding
%2b instead of + for search terms:
// Old (may fail)
$results = Xero::api('contacts')->search('name', 'John+Doe');
// New (recommended)
$results = Xero::api('contacts')->search('name', 'John%2bDoe');
Exception Response Access
try {
// ...
} catch (\Calcinai\Xero\Exceptions\XeroException $e) {
$response = $e->getResponse(); // Available in all exceptions
}
PHP 8.4 Deprecation
// Old (may trigger deprecation)
public function process($data = null) {}
// New (recommended)
public function process(?array $data = null) {}
Model Property Access
Details on Payment) may not exist on older Xero versions:
if (property_exists($payment, 'Details')) {
$details = $payment->Details;
}
Check for New Model Methods
$model = Xero::api('batchPayments')->find($id);
dd(get_class_methods($model)); // Verify new methods exist
Search Query Testing
// Test search encoding
$term = 'John+Doe';
$encoded = str_replace('+', '%2b', $term);
$results = Xero::api('contacts')->search('name', $encoded);
Exception Response Inspection
try {
// ...
} catch (\Calcinai\Xero\Exceptions\XeroException $e) {
dd($e->getResponse()); // Inspect raw response
}
PHP 8.4 Strict Mode
Enable strict types in php.ini to catch issues early:
declare(strict_types=1);
Model Property Validation
$payment = Xero::api('payments')->find($id);
if (!isset($payment->Details)) {
// Handle missing property
}
Custom History Trait Implementation Extend models to support history:
namespace App\Xero;
use Calcinai\Xero\Traits\HistoryTrait;
class CustomBatchPayment extends \Calcinai\Xero\Models\BatchPayment
{
use HistoryTrait;
}
Search Query Helper
function xeroSearchEncode($term) {
return str_replace('+', '%2b', urlencode($term));
}
Exception Response Handler
class XeroExceptionHandler
{
public static function handle(XeroException $e) {
$response = $e->getResponse();
// Custom logic
}
}
PHP 8.4 Type Migration
// Before
public function __construct($param) {}
// After
public function __construct(?string $param = null) {}
Model Property Accessor
function getPaymentDetails($payment) {
return property_exists($payment, 'Details')
? $payment->Details
: null;
}
How can I help you explore Laravel packages today?