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

Laravel Afas Rest Connector Laravel Package

we-simply-code/laravel-afas-rest-connector

A Laravel package that wraps the AFAS REST API, offering a fluent interface to query, create, update, and delete AFAS data. Includes configurable authentication, request handling, and integration with Laravel's service container and facades.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish

composer require we-simply-code/laravel-afas-rest-connector
php artisan vendor:publish --tag=afas-config

Add the credentials to .env (key names may vary slightly per release — open the published config/afas.php and map them):

AFAS_URL=https://12345.rest.afas.online/Profit/ABCDE
AFAS_TOKEN=base64-encoded-token-from-afas-profit

The token is generated in AFAS Profit under Beheer → App connector (or via the user's "token" screen), and it is the value you send as Authorization: AfasToken <token>. It is not a bearer token — don't prefix it with Bearer.

Where to look first

  1. config/afas.php — the only file that matters initially. It holds the base URL and token; every call is relative to it.
  2. The facade / service class registered by the package's service provider (php artisan about or grep the vendor dir for class Afas). This is your entry point for get/post/put/delete.
  3. The README's connector examples — they are thin wrappers around GET /connectors/{name}.

First use case: read one GetConnector

The smallest useful call pulls a GetConnector and returns the results array:

use WeSimplyCode\AfasRestConnector\Facades\Afas;

$response = Afas::get('Profit_Article', [
    'take' => 10,
]);

$articles = $response['results'] ?? [];

Run it in php artisan tinker before writing any application code. If you get a 401, the token or URL is wrong; if you get a 404, the connector name is wrong (they are case-sensitive and environment-specific). Getting one happy-path call working in tinker is 80% of the integration effort.

Implementation Patterns

Wrap the facade, don't sprinkle it

Put a single AfasClient service around the package so connector names, retries, logging, and pagination live in one place:

class AfasClient
{
    public function articles(int $take = 100): array
    {
        return $this->fetchAll('Profit_Article', [], $take);
    }

    protected function fetchAll(string $connector, array $params, int $take): array
    {
        $skip = 0;
        $all  = [];

        do {
            $page = Afas::get($connector, $params + [
                'skip' => $skip,
                'take' => $take,
            ])['results'] ?? [];

            $all  = array_merge($all, $page);
            $skip += $take;
        } while (count($page) === $take);

        return $all;
    }
}

The AFAS response envelope contains @totalsize, @count and results. Prefer looping until a page is short over trusting @totalsize — @totalsize is often capped and reflects the filtered set, not what you can actually page through.

Filtering

AFAS filters are positional triplets, which is the single biggest source of confusion:

Afas::get('Profit_Article', [
    'filterfieldids'  => 'Itemcode,Description',
    'filtervalues'    => "'ABC-1','Widget'",
    'operatortypes'   => '1,2',   // 1 = equal, 2 = contains (verify in AFAS docs)
    'orderbyfieldids' => '-Itemcode',
]);

Treat this as a reusable AfasFilter value object. Build the quoted string from an array of [field, operator, value] tuples so callers never hand-write commas and quotes.

Writes go through Update connectors

Reads use Get connectors (Profit_X); writes use the matching Update connector (Profit_X as well in many cases, but the name differs for some entities). Wrap each write in a small method and always inspect the returned {"fault": ...} payload — a failed update frequently returns HTTP 200 with a per-row error body.

$result = Afas::put('Profit_Article', [
    'Element' => ['Fields' => ['Itemcode' => 'ABC-1', 'Description' => 'New']],
]);

if (! empty($result['fault'])) {
    throw new AfasUpdateException($result['fault']['errorMessage']);
}

Push syncs to the queue

AFAS is not a fast API. Any job that touches more than a handful of rows belongs in a queued job with $tries = 3, backoff() of 30–60s, and a per-run rate limit (RateLimited middleware or Redis::throttle). Batch updates one record per request; there is no bulk endpoint.

Cache reference data

Connectors like Profit_Department, Profit_Country, or Profit_Administration change weekly, not hourly. Cache::remember('afas.departments', now()->addHours(6), fn () => app(AfasClient::class)->departments()) removes a large chunk of latency and rate-limit pressure.

Test without hitting AFAS

Everything funnels through Laravel's HTTP client, so Http::fake() works:

Http::fake([
    '*/connectors/Profit_Article*' => Http::response([
        '@count' => 1,
        'results' => [['Itemcode' => 'ABC-1']],
    ]),
]);

Fake the AfasClient, not the facade, in feature tests — the facade resolves lazily and makes assertions awkward.

Gotchas and Tips

  • Token expiry is silent-ish. AFAS tokens have a long but finite lifetime (often a year), and the failure mode is a 401 deep inside a queued job. Store the expiry date in your ops runbook and add a health-check command that calls a cheap connector daily.
  • Quoting rules. Numeric filter values are unquoted, strings must be single-quoted inside the query string. Getting this wrong usually returns zero results rather than an error, which is the worst kind of bug.
  • take limits. AFAS caps page size (commonly 5000, with 100–500 recommended for responsiveness). Asking for too much returns an error or truncates; requesting a sane page size and paginating is both faster and safer.
  • skip/take pagination drifts. If records are inserted while you paginate, you will skip or duplicate rows. For delta syncs, filter on a modified timestamp (filterfieldids=Modified,filtervalues='2024-01-01T00:00:00') instead of relying on offsets.
  • Date formats. AFAS is picky. Use ISO-8601 (Y-m-d\TH:i:s) and be aware that some connectors expect local time without an offset. Test one record before writing a date-range loop.
  • Errors are inconsistent. 400/500 return {"fault": {...}}; some business-rule failures return 200 with a fault in the body. Always check the body, not just the status code. Log the full response on failure — AFAS error messages are terse and the request context is what makes them debuggable.
  • No built-in retry. A bare 429 or transient 5xx propagates straight to your caller. Add ->retry(3, 1000) semantics in your wrapper, and honour Retry-After if present.
  • Config caching bites during deploy. php artisan config:cache freezes AFAS_TOKEN. If you rotate tokens via a secrets manager at runtime, read from config() only after a cache clear, or inject the token through a custom binding.
  • Version pinning. This package is young (single-digit stars, no dependents, a release dated in the future). Pin an exact version in composer.json and read the diff before bumping — the public surface is small enough that you could vendor the two relevant classes if maintenance stalls.
  • Extension points. Because the package is thin, the cheapest escape hatch is to stop using the facade for one stubborn endpoint and call Http::withToken(config('afas.token'))->baseUrl(config('afas.url')) directly. That's supported by design, not a workaround.
  • Never log the token. Laravel's Http::withOptions(['debug' => ...]) and Guzzle middleware will happily dump the Authorization header into your logs. Scrub it in any custom middleware you add.
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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle