Install the Bundle
composer require aibianchi/exact-online-bundle
Configure Exact Online Accounts
Add your Exact Online credentials to config/packages/exact_online.yaml:
exact_online:
Belgium:
baseUrl: https://start.exactonline.be/
apiUrl: api/v1
authUrl: api/oauth2/auth
tokenUrl: api/oauth2/token
redirectUrl: https://yourdomain.com/exact-request
clientId: YOUR_CLIENT_ID
clientSecret: YOUR_CLIENT_SECRET
Update Database Schema
php bin/console doctrine:schema:update --force
First Authentication Redirect users to Exact Online’s OAuth flow:
// In a controller (e.g., ExactRequestController)
public function authenticate(Request $request, ExactManager $exactManager) {
$code = $request->query->get('code');
$exactManager->init($code, "Belgium"); // First-time auth
}
Visit https://yourdomain.com/exact-request to trigger the OAuth flow.
use aibianchi\ExactOnlineBundle\Manager\ExactManager;
public function listAccounts(ExactManager $exactManager) {
$accounts = $exactManager->getModel("Account")->getList(1, 10); // Page 1, 10 items
foreach ($accounts as $account) {
dump($account->getName());
}
}
ExactManager into controllers/services:
public function __construct(private ExactManager $exactManager) {}
"Belgium") to switch contexts:
$exactManager->refreshToken("France"); // Switch to France account
CRUD Operations
// Create
$item = new Item();
$item->setCode('TEST123')->setDescription('Test Item');
$exactManager->persist($item);
// Read
$account = $exactManager->getModel("Account")->find("GUID_HERE");
// Update
$account->setWebsite("https://example.com");
$exactManager->update($account);
// Delete
$exactManager->remove($account);
Querying with Filters
$criteria = ['BusinessType' => 'Customer'];
$accounts = $exactManager->getModel("Account")->findBy($criteria, ['Name', 'Email']);
Pagination
$page = 2;
$perPage = 20;
$accounts = $exactManager->getModel("Account")->getList($page, $perPage);
Token Refresh Schedule a job to refresh tokens before expiration (e.g., via Symfony Messenger or cron):
$exactManager->refreshToken("Belgium");
exact.online.token.refreshed events.Model class.ExactOnlineException:
try {
$exactManager->getModel("Account")->find("INVALID_GUID");
} catch (\Exception $e) {
// Log or notify
}
Token Expiry
code expires after 10 minutes. Use refreshToken() for subsequent calls.Country-Specific Config
"Belgium") will default to the first configured account.refreshToken() or init().Guzzle Version Mismatch
^6.2 in composer.json.Database Schema Updates
doctrine:schema:update without backing up may overwrite existing data.doctrine:migrations:diff) instead of --force.Redirect URL Mismatch
redirectUrl in config must match the Exact Online app registration.Enable API Debugging Configure Guzzle middleware to log requests/responses:
$exactManager->getClient()->getEmitter()->attach(
new \GuzzleHttp\Middleware::tap(function ($request, $options) {
error_log($request->getUri());
})
);
Check Token Validity Manually verify tokens via Exact Online’s OAuth playground.
Custom Endpoints
Extend the ExactManager to support non-CRUD endpoints:
$response = $exactManager->getClient()->request('GET', '/api/v1/Reports');
Model Overrides
Create a custom model class (e.g., CustomAccount) extending the bundle’s Account model to add fields/methods.
Authentication Flow
Override the OAuth redirect logic by extending the ExactAuthenticator service.
Rate Limiting
Implement a decorator around ExactManager to handle API rate limits:
$manager->setRateLimiter(new CustomRateLimiter());
Batch Operations: Use Exact Online’s bulk endpoints (if supported) to reduce API calls.
Caching: Cache frequent queries (e.g., getList()) with Symfony’s cache component:
$cacheKey = "accounts_page_1";
$accounts = $this->cache->get($cacheKey, function() use ($exactManager) {
return $exactManager->getModel("Account")->getList(1, 10);
});
Async Processing: Offload long-running operations (e.g., bulk updates) to Symfony Messenger or a queue worker.
How can I help you explore Laravel packages today?