Exact PHP Client

repository·main·Indexed 41 days ago

https://github.com/picqer/exact-php-client

A PHP client library for the Exact Online API that enables integration for managing invoices, journal entries, and products. It provides a Connection class for OAuth2 authentication, token exchange, and HTTP requests, while featuring automatic handling of API rate limits, PHP Generators for large datasets, and support for Guzzle 6 and 7 (starting from version 3).

Tokens
12.9K
Snippets
22
Records
32
Agent score
51%

What's inside Exact PHP Client

  1. Note on entity property completeness

    main
    The current entity models provided by the library may not contain every available property from the Exact API. If you encounter a property that is missing from the existing entities, you can extend them or submit a Pull Request to the repository to include them.
  2. Handle API rate limits

    main

    Exact Online enforces minutely and daily rate limits. If you exceed these, the library throws an ApiException with code 429.

    Monitoring Limits

    You can inspect current limits using the following Connection methods:

    • $connection->getDailyLimit(): Total daily calls allowed.
    • $connection->getDailyLimitRemaining(): Remaining calls for today.
    • $connection->getDailyLimitReset(): Timestamp when the daily limit resets.
    • $connection->getMinutelyLimit(): Calls allowed per minute.
    • $connection->getMinutelyLimitRemaining(): Remaining calls for this minute.
    • $connection->getMinutelyLimitReset(): Timestamp when the minutely limit resets.

    Automatic Waiting

    You can instruct the client to automatically pause execution until the minutely limit resets by calling: $connection->setWaitOnMinutelyRateLimitHit(true);

  3. Manage large datasets with PHP Generators

    main

    To prevent memory exhaustion when retrieving large sets of data, use the generator methods. These allow you to iterate over results one by one instead of loading the entire collection into memory.

    Use getAsGenerator() for a full list or filterAsGenerator($filter) to iterate over a filtered subset.

    $item = new \Picqer\Financials\Exact\Item($connection);
    $item->getAsGenerator();
    $item->filterAsGenerator('IsWebshopItem eq 1');
  4. Authorize the integration with Exact Online

    main

    Before you can make API calls, you must authorize your application. This involves redirecting the user to Exact Online to log in and grant permissions. You will need your Client ID, Client Secret, and the Callback URL configured in the Exact App Center.

    Use the redirectForAuthorization() method on a Connection instance to initiate the oAuth dance.

    $connection = new \Picqer\Financials\Exact\Connection();
    $connection->setRedirectUrl('CALLBACK_URL'); // Same as entered online in the App Center
    $connection->setExactClientId('CLIENT_ID');
    $connection->setExactClientSecret('CLIENT_SECRET');
    $connection->redirectForAuthorization();
  5. Complete the connection setup and token exchange

    main

    After authorization, Exact redirects the user back to your Callback URL with an authorizationcode parameter. You must exchange this code for an accesstoken and a refreshtoken using the connect() method.

    To maintain the connection without re-authorizing the user every time, you should store the accesstoken, refreshtoken, and the token's expiration timestamp in your database. On subsequent requests, populate these values into the Connection object before calling connect().

    To prevent multiple simultaneous requests from trying to refresh the token at the same time, you can optionally set lock and unlock callbacks using setAcquireAccessTokenLockCallback() and setAcquireAccessTokenUnlockCallback().

    $connection = new \Picqer\Financials\Exact\Connection();
    $connection->setRedirectUrl('CALLBACK_URL');
    $connection->setExactClientId('CLIENT_ID');
    $connection->setExactClientSecret('CLIENT_SECRET');
    
    if (getValue('authorizationcode')) {
        $connection->setAuthorizationCode(getValue('authorizationcode'));
    }
    
    if (getValue('accesstoken')) {
        $connection->setAccessToken(unserialize(getValue('accesstoken')));
    }
    
    if (getValue('refreshtoken')) {
        $connection->setRefreshToken(getValue('refreshtoken'));
    }
    
    if (getValue('expires_in')) {
        $connection->setTokenExpires(getValue('expires_in'));
    }
    
    try {
        $connection->connect();
    } catch (\Exception $e) {
        throw new Exception('Could not connect to Exact: ' . $e->getMessage());
    }
    
    // Save tokens for next time
    setValue('accesstoken', serialize($connection->getAccessToken()));
    setValue('refreshtoken', $connection->getRefreshToken());
    setValue('expires_in', $connection->getTokenExpires());
    
    // Prevent race conditions during token refresh
    $connection->setAcquireAccessTokenLockCallback('CALLBACK_FUNCTION');
    $connection->setAcquireAccessTokenUnlockCallback('CALLBACK_FUNCTION');
  6. Handle OAuth2 authorization flow

    main

    If your application does not yet have an access token or refresh token, you must redirect the user to Exact Online to authorize your app.

    1. Use getAuthUrl() to generate the URL for the authorization request.
    2. Use redirectForAuthorization() to immediately trigger a PHP header redirect to that URL.
    3. Optionally use setState(string $state) to pass a CSRF token to the authorization URL, which will be returned in the callback.
    4. Use setForceLogin(bool $forceLogin) to force the user to enter their credentials even if they are already logged into Exact Online.
    // Generate the URL to redirect the user to
    $authUrl = $connection->getAuthUrl();
    
    // Or redirect immediately
    $connection->redirectForAuthorization();
  7. Troubleshoot 'Error 400: Please add a $select or a $top=1' error

    main

    If you encounter an ApiException stating that a $select or $top=1 statement is required, it is because the Exact API requires explicit field selection or result limiting to prevent over-fetching.

    To resolve this, pass the selection or top parameter to the filter() method.

    Example: Selecting specific fields

    $test = new \Picqer\Financials\Exact\GeneralJournalEntry($connection);
    var_dump($test->filter('', '', 'EntryID, FinancialYear'));

    Example: Limiting to one result ($top=1)

    $test = new \Picqer\Financials\Exact\GeneralJournalEntry($connection);
    var_dump($test->filter('', '', '', ['$top'=> 1]));
  8. Manage Webhook subscriptions and authentication

    main

    Webhook subscriptions are managed via the WebhookSubscription entity.

    To authenticate incoming webhook calls, use the Authenticatable trait. Pass the full JSON request body and your Exact-supplied Webhook secret to the authenticate method; it returns a boolean indicating if the request is valid.

  9. Configure the base URL for different Exact countries

    main

    By default, the client connects to the Netherlands (NL) instance. To connect to other Exact regions (e.g., Germany), use setBaseUrl() with the appropriate URL from the Exact developers guide.

    $connection = new \Picqer\Financials\Exact\Connection();
    $connection->setRedirectUrl('CALLBACK_URL');
    $connection->setExactClientId('CLIENT_ID');
    $connection->setExactClientSecret('CLIENT_SECRET');
    $connection->setBaseUrl('https://start.exactonline.de');
  10. BulkGLClassification Data Model

    main

    The BulkGLClassification class represents Bulk General Ledger (GL) Classification resources in Exact Online. It is a storable model that can be used to retrieve, create, or update GL classification elements.

    Key properties include:

    • ID: The primary key.
    • Code: A unique identifier code.
    • Name: A unique name within the namespace.
    • Division: The division ID. This is mandatory for division-specific taxonomies and empty for general taxonomies.
    • Type: The type of the element.
    • Balance: Used for amount concepts; can be 'debit' or 'credit'.
    • PeriodType: Used for item concepts; can be 'instant' (for balance types) or 'duration' (for P&L types).
    • Parent: The parent element (used for reporting schemes).
    • Description: Description of the element (used for division-specific taxonomies).

    This model uses the Query\Findable and Persistance\Storable traits, allowing it to be queried and persisted via the API endpoint bulk/Financial/GLClassifications.