voipcompetencecenter/weclappclient

Ein PHP-Client für die Weclapp REST-API v2 mit vollständiger Feature-Unterstützung

Maintainers

Package info

github.com/VCC-Common-Tools/weclappclient

pkg:composer/voipcompetencecenter/weclappclient

Transparency log

Statistics

Installs: 15

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

2.2.0 2026-07-03 06:54 UTC

This package is auto-updated.

Last update: 2026-07-03 06:54:56 UTC


README

A comprehensive and flexible PHP client for interacting with the Weclapp REST API v2.
It supports structured queries, pagination, sorting, CRUD operations, and all advanced API features.
Responses are returned as native PHP arrays with full RFC 7807 error handling.

🚀 What's New in v2.0.0

  • API v2 Migration: Full support for weclapp API v2 with legacy v1 compatibility
  • Advanced Filtering: OR conditions, grouping, raw filter expressions (Beta)
  • Custom Attributes: Type-safe filtering for custom fields with automatic data conversion
  • Special Parameters: Elegant handling of endpoint-specific parameters (entityName, entityId)
  • Performance Optimization: Referenced entities, selective properties, additional properties
  • Enhanced Updates: Partial updates, dry-run mode, null serialization
  • Complete Error Handling: RFC 7807 compliant with 40+ error types
  • Backward Compatible: Minimal breaking changes, easy migration from v1.x

📦 Installation

composer require voipcompetencecenter/weclappclient

🔧 Setup

use WeclappClient\Core\WeclappClient;

// Default: API v2
$client = new WeclappClient('your-subdomain', 'your-api-key');

// Legacy: API v1 (if needed)
$client = new WeclappClient('your-subdomain', 'your-api-key', null, 1);

HTTP options: timeouts & retries (v2.2.0)

The client applies sane defaults for production use and automatically retries rate-limited requests (HTTP 429) and connection errors with exponential backoff. You can override these via the optional 5th constructor argument:

$client = new WeclappClient('your-subdomain', 'your-api-key', null, 2, [
    'timeout'         => 30,   // request timeout in seconds (default 30)
    'connect_timeout' => 10,   // connection timeout in seconds (default 10)
    'max_retries'     => 3,    // retries on HTTP 429 / connection errors (default 3)
    'retry_delay_ms'  => 1000, // base backoff in ms, doubled per attempt (default 1000)
]);

Note: timeout/connect_timeout apply only to the internally created HTTP client. If you inject your own Guzzle client, configure timeouts there. Generic 5xx responses are not retried automatically to avoid duplicate writes on non-idempotent operations (e.g. POST).

🔍 Querying with the QueryBuilder

$results = $client->query('/customer')
    ->whereEq('customerType', 'CUSTOMER')
    ->whereLike('company', '%GmbH%')
    ->orderBy('lastModifiedDate', 'desc')
    ->limit(10)
    ->all();

Supported Filter Methods

Method Description
where($field, $op, $val) Custom filter condition
whereEq('field', 'val') Equals
whereNe('field', 'val') Not equal
whereGt, whereGe Greater than, greater or equal
whereLt, whereLe Less than, less or equal
whereLike, whereILike (i)LIKE search pattern
whereNotLike, whereNotILike NOT LIKE pattern
whereIn('field', [...]) Field value in given list
whereNotIn('field', [...]) Field value NOT in given list
whereNull('field') Field is null
whereNotNull('field') Field is not null

Advanced Filtering (v2.0.0)

// OR Conditions
$results = $client->query('/party')
    ->whereEq('firstName', 'Max')
    ->orWhere('lastName', 'eq', 'Mustermann')
    ->orWhere('email', 'like', '%@example.com')
    ->getResult();

// OR Grouping
$results = $client->query('/party')
    ->orWhereGroup('location', function($q) {
        $q->orWhere('city', 'eq', 'Berlin')
          ->orWhere('city', 'eq', 'München');
    })
    ->orWhereGroup('status', function($q) {
        $q->orWhere('active', 'eq', true)
          ->orWhere('verified', 'eq', true);
    })
    ->getResult();

// Raw Filter Expressions (Beta)
$results = $client->query('/party')
    ->whereRaw('(age >= 18) and (customer = true)')
    ->getResult();

Custom Attributes (v2.0.0)

Type-safe filtering for custom fields with automatic data conversion:

// String Custom Attributes
$results = $client->query('/party')
    ->whereCustomAttributeString('customer-note', 'like', '%VIP%')
    ->getResult();

// Boolean Custom Attributes
$results = $client->query('/party')
    ->whereCustomAttributeBoolean('is-premium', 'eq', true)
    ->getResult();

// Number Custom Attributes
$results = $client->query('/party')
    ->whereCustomAttributeNumber('credit-limit', 'gt', 10000)
    ->getResult();

// Date Custom Attributes (automatic string → timestamp conversion)
$results = $client->query('/party')
    ->whereCustomAttributeDate('last-contact', 'gt', '2024-01-01')
    ->getResult();

// OR-Filter with Custom Attributes
$results = $client->query('/party')
    ->orWhereCustomAttribute('customer-tier', 'eq', 'Gold', 'stringValue')
    ->orWhereCustomAttribute('customer-tier', 'eq', 'Platinum', 'stringValue')
    ->getResult();

Special Parameters (v2.0.0)

Elegant handling of endpoint-specific parameters without operator suffixes:

// Document Endpoint - requires entityName and entityId
$documents = $client->query('/document')
    ->entityName('party')
    ->entityId('12345')
    ->whereLike('name', '%Rechnung%')
    ->orderBy('createdDate', 'desc')
    ->getResult();

// Comment Endpoint - requires entityName and entityId
$comments = $client->query('/comment')
    ->entityName('party')
    ->entityId('12345')
    ->orderBy('createdDate', 'desc')
    ->getResult();

// Generic parameters for any endpoint
$results = $client->query('/custom-endpoint')
    ->param('customParam', 'customValue')
    ->param('anotherParam', 123)
    ->getResult();

Sorting

->orderAsc('fieldName')
->orderDesc('fieldName')
->orderBy('fieldName', 'desc') // 'asc' is default

Limiting and Pagination

->limit(50)          // fetches up to 50 records across pages
->page(2, 25)        // page 2 with 25 entries (classic pagination)

🔢 Counting

$count = $client->query('/customer')->whereEq('customerType', 'CUSTOMER')->count();

🎯 Getting Single Records

// Get first matching record
$customer = $client->query('/customer')
    ->whereEq('customerType', 'CUSTOMER')
    ->first();

// Get specific record by ID
$customer = $client->query('/customer')->get($id);

⚡ Performance Optimization (v2.0.0)

// Selective Properties - Only fetch needed fields
$results = $client->query('/article')
    ->properties(['id', 'name', 'unitId', 'articleCategoryId'])
    ->getResult();

// Referenced Entities - Load related data in one request
$results = $client->query('/article')
    ->includeReferencedEntities(['unitId', 'articleCategoryId'])
    ->getResult();

// Additional Properties - Computed fields
$results = $client->query('/article')
    ->additionalProperties('currentSalesPrice')
    ->getResult();

// Combined optimization
$results = $client->query('/article')
    ->properties(['id', 'name', 'unitId'])
    ->includeReferencedEntities(['unitId'])
    ->additionalProperties('currentSalesPrice')
    ->serializeNulls() // Include null values explicitly
    ->getResult();

🛠️ CRUD Operations

// Fetch single object by ID
$customer = $client->query('/customer')->get($id);

// Create
$created = $client->query('/customer')->create([
    'company' => 'Test GmbH',
    'customerType' => 'CUSTOMER',
    'partyType' => 'ORGANIZATION'
]);

// Update (Traditional)
$updated = $client->query('/customer')->update([
    'id' => $created['id'],
    'company' => 'Updated GmbH'
]);

// Partial Update (v2.0.0) - Only update specified fields
$updated = $client->query('/customer')->partialUpdate([
    'id' => $created['id'],
    'version' => $created['version'],
    'email' => 'new.email@example.com'
]);

// Dry-Run Mode (v2.0.0) - Validate without executing
$validation = $client->query('/customer')
    ->dryRun()
    ->create([
        'company' => 'Test Corp',
        'customerType' => 'CUSTOMER'
    ]);

// Delete
$success = $client->query('/customer')->delete($created['id']);

🛡️ Error Handling (v2.0.0)

The client now provides comprehensive RFC 7807 compliant error handling:

use WeclappClient\Exception\WeclappApiException;
use WeclappClient\Exception\WeclappValidationError;

try {
    $result = $client->query('/party')->create($data);
} catch (WeclappApiException $e) {
    // RFC 7807 compliant error information
    echo "Error Type: " . $e->getType();           // URI reference to error type
    echo "Title: " . $e->getTitle();               // Short summary
    echo "Detail: " . $e->getDetail();             // Detailed explanation
    echo "Instance: " . $e->getInstance();         // URI to affected entity
    
    // Validation errors (if any)
    foreach ($e->getValidationErrors() as $error) {
        echo "Field: " . $error->location;         // JsonPath to field
        echo "Message: " . $error->detail;         // Field-specific error
        echo "Allowed: " . implode(', ', $error->allowed ?? []); // Valid values
    }
}

Error Types

The client supports 40+ error types including:

  • Main Errors: context, forbidden, optimistic_lock, validation, etc.
  • Validation Errors: email, pattern, size, reference, enum, etc.

📄 Response Format

All queries return Weclapp-formatted arrays (i.e. response['body']['result']). Example:

[
    'id' => 123456,
    'company' => 'Sample Company GmbH',
    'customerNumber' => 'C-1000',
    // ...
]

🔄 Migration from v1.x

Breaking Changes (Minimal)

  1. Default API Version: Now uses v2 by default (v1 still available)
  2. Update Method: New signature update(array $data, bool $ignoreMissingProperties = false)

Migration Steps

// v1.x (old)
$client = new WeclappClient('tenant', 'token');

// v2.0.0 (automatic migration)
$client = new WeclappClient('tenant', 'token'); // Uses v2 by default

// Legacy v1 (if needed)
$client = new WeclappClient('tenant', 'token', null, 1);

New Features Available

All new features are optional and backward compatible:

// Use new advanced filtering
$results = $client->query('/party')
    ->whereNotNull('email')
    ->orWhere('firstName', 'eq', 'Max')
    ->getResult();

// Use custom attributes (replaces legacy customField methods)
$results = $client->query('/party')
    ->whereCustomAttributeString('customer-note', 'like', '%VIP%')
    ->whereCustomAttributeBoolean('is-premium', 'eq', true)
    ->getResult();

// Use special parameters for document/comment endpoints
$results = $client->query('/document')
    ->entityName('party')
    ->entityId('12345')
    ->getResult();

// Use performance optimizations
$results = $client->query('/article')
    ->properties(['id', 'name'])
    ->includeReferencedEntities(['unitId'])
    ->getResult();

// Use enhanced updates
$client->query('/party')->partialUpdate($data);
$client->query('/party')->dryRun()->create($data);

🧪 Testing

The test suite is split into two parts:

  • unit – fast, isolated tests using a mocked HTTP client. No credentials or network access required. These run in CI.
  • integration – tests against a real Weclapp system. Requires valid credentials in tests/.env (WCLP_TEST_SUBDOMAIN, WCLP_TEST_API_KEY; see tests/.env.example).
# Unit tests only (default, no credentials needed)
composer test

# Integration tests against a live system (needs tests/.env)
composer test-integration

# Everything
composer test-all

🧩 For Developers: Reusing the QueryBuilder

The class AbstractBaseQueryBuilder provides the technical foundation for the QueryBuilder. It encapsulates reusable API logic for filters, pagination, sorting, and counting.

You can use this class as a base to implement your own API clients in a similar architecture – independent of Weclapp.

class MyApiQueryBuilder extends AbstractBaseQueryBuilder
{
    public function all(): array
    {
        // custom logic to retrieve all data
    }
}

📋 Changelog

See CHANGELOG.md for detailed version history.

📄 License

MIT License - see LICENSE file for details.