Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Xero Bundle Laravel Package

fourlabs/xero-bundle

A PHP package that bundles Xero accounting integration for your application, offering configuration and service wiring to connect to the Xero API and work with invoices, contacts, and other accounting data with less boilerplate.

View on GitHub
Deep Wiki
Context7

Getting Started

This bundle is thin — it wires calcinai/xero-php into the Symfony container and maps a config block onto the client constructor. There is very little code, so read the three files that constitute it before anything else:

  1. src/DependencyInjection/Configuration.php — the exact config keys (consumer key/secret, RSA private key or cert path, callback URI) and their defaults.
  2. src/DependencyInjection/*Extension.php — how those keys are turned into service arguments, and the service alias you should inject.
  3. src/Resources/config/services.* — the service id you'll type-hint.

Minimal path:

composer require fourlabs/xero-bundle:^1.2 calcinai/xero-php:^1.8
// config/bundles.php
FourLabs\XeroBundle\FourLabsXeroBundle::class => ['all' => true],
# config/packages/xero.yaml — confirm the root key in Configuration.php first
four_labs_xero:
    consumer_key: '%env(XERO_KEY)%'
    consumer_secret: '%env(XERO_SECRET)%'
    private_key: '%kernel.project_dir%/var/xero/privatekey.pem'

First use case: prove auth before building anything. Inject the client into a console command and do one read — getConnections(), then a ->load('Accounting\\Contact')->execute() — so you learn immediately whether the auth flow works at all against today's Xero. Do not start with write paths.

1.2.0 changes nothing you can use. The release is a single dependency bump to calcinai/xero-php 1.8; the bundle's config keys, service ids, and constructor wiring are untouched. So there is no migration to do within the bundle — the entire question is what that 1.8 constraint resolves to. Before writing a line of integration code, run composer show calcinai/xero-php and composer why calcinai/xero-php. If the resolved tree still drags in the XeroPHP\Application\PrivateApplication / RSA-signature OAuth 1.0a path, the bump is cosmetic and the auth risk described below still stands.

If you're a Laravel developer: this is a Symfony bundle and will not boot under Laravel. It remains a reference implementation for how to configure calcinai/xero-php, and 1.2.0 doesn't change that. Register the client in your own XeroServiceProvider rather than pulling Symfony bundle machinery in for one class. The bump does make the ^1.8 constraint easier to satisfy alongside a modern Laravel's Guzzle, which is worth a composer update --dry-run to confirm.

Implementation Patterns

  • Inject, never instantiate. Constructor-inject the aliased client service. If the bundle exposes an interface, alias to that so tests can swap it.
  • Own your token storage. The bundle wires credentials, not persistence. Store access/refresh tokens and the tenant (xero-tenant-id) per connection in your DB; refresh proactively on expiry and reactively on 401.
  • Multi-tenant is the default shape. One application, N Xero connections. getConnections() returns them; every Accounting API call is tenant-scoped. Don't hold a mutable "current tenant" on a singleton client — resolve a tenant-scoped client per job/request.
  • Sync via queue, not request. Mirror contacts/invoices using idempotent upserts keyed on Xero's ContactID/InvoiceID, and use UpdatedDateUTC for delta pulls. Rate limits are ~60 calls/min and ~5,000/day per tenant, so a scheduled job with a rate limiter is the only workable pattern.
  • Batch writes and read errors. Use executeWithErrors() and inspect the returned validation payload — a partially failed batch returns 200 with per-item failures.
  • Wrap it. Put a thin repository/service layer over the client (syncContacts, createInvoice). The bundle is unmaintained and the 1.8 bump is the last thing likely to happen to it; the wrapper is your escape hatch when you replace it.
  • Upgrade as a contained experiment. Treat the 1.2.0 bump as a dependency change, not a feature release: branch, composer update calcinai/xero-php --with-dependencies, then run the auth smoke command from Getting Started against a sandbox tenant before merging. If it fails, the revert is a one-line constraint change — keep it that way.
  • Testing: fake at your service boundary, or bind a stub client in the container. Don't mock the HTTP layer.

Gotchas and Tips

  • The 1.8 bump does not resolve the OAuth question — verify it explicitly. calcinai/xero-php 1.x is the OAuth 1.0a / RSA-signature line (PrivateApplication, PublicApplication, PartnerApplication); OAuth 2 support arrived in the 2.x line (with a separate OAuth2 provider package). Xero has retired OAuth 1.0a, so a ^1.8 constraint that resolves inside 1.x is very likely still unusable for new apps and for any app re-authorising today. Confirm the resolved major before committing to this package — this remains the single thing that kills it for most projects.
  • A dependency bump can break your app's shared dependencies. 1.8 may shift the Guzzle major, the minimum PHP version, or the psr/* constraints. Run composer update --dry-run first and read the full resolved diff; a Guzzle major bump in a Laravel app touches HTTP clients you didn't intend to change.
  • No new bundle features, no deprecations, no BC breaks at the bundle layer. Config keys, extension alias, and service ids are identical to 1.1.x. Don't go looking for a changelog entry that explains a behaviour change — there isn't one.
  • Two stars, zero dependents. No community, no fixes upstream. Expect to vendor or fork; budget for it. A single-commit release after years of silence is a maintenance twitch, not a revival.
  • Config key mismatch is the classic failure: the extension alias in getContainerExtension() doesn't always match the package name. If your YAML is silently ignored, that's why.
  • Container is compiled. After changing config, bin/console cache:clear --env=prod. In prod the container is baked — config changes without a deploy do nothing.
  • Key/cert file paths must be absolute, readable by the php-fpm user, and (if passphrase-protected) have the passphrase supplied separately. Permission errors surface as opaque auth failures — and under OAuth 1.0a you'll hit them constantly, since every request is signed.
  • Debugging: attach Guzzle logging middleware to the client and log raw response bodies. Catch Calcinai\XeroPHP\Exception\* and dump the decoded error array — the human-readable message is almost always in ValidationErrors or Message, not getMessage().
  • Concurrent token refresh across workers will invalidate itself. Guard refresh with a lock.
  • Extension points: since the bundle is thin, override its service definition in your own services.yaml under the same id rather than forking. That covers logging middleware, custom token stores, and timeouts.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource