blindaue/phpade

PHP 8 ADE Web API Client

Maintainers

Package info

git.unistra.fr/e.blindauer/phpade

pkg:composer/blindaue/phpade

Statistics

Installs: 101

Dependents: 0

Suggesters: 0

1.4.1 2026-03-22 12:50 UTC

This package is not auto-updated.

Last update: 2026-04-19 13:09:00 UTC


README

PHP 8 client for ADE Web API from Adesoft.

This is an unofficial development. I am in no way related to this company. Use it at your own risk.

Installation

composer require blindaue/phpade

Configuration

Environment Variables

Create a .env.local file in your project root:

ADE_WEB_API_URL="https://ade.domain.tld/jsp/webapi"
ADE_WEB_API_LOGIN="your_login"
ADE_WEB_API_PASSWORD="your_password"

Or use environment variables directly:

export ADE_WEB_API_URL="https://ade.domain.tld/jsp/webapi"
export ADE_WEB_API_LOGIN="your_login"
export ADE_WEB_API_PASSWORD="your_password"

Standalone Usage

<?php

require_once 'vendor/autoload.php';

use Phpade\AdeWebAPI;

$api = new AdeWebAPI();
$api->connect();
$api->setProject(4);

$projects = $api->getProjects(['detail' => 4]);
$instructors = $api->getResources(['category' => 'instructor', 'detail' => 6]);

foreach ($instructors as $instructor) {
    echo $instructor['name'] . ' - ' . ($instructor['email'] ?? 'no email') . "\n";
}

$api->disconnect();

Detail Levels (ADE Reader 10/07/2025)

Recommended levels for current ADE Reader docs:

  • getEvents: use detail=4 for base event data.
  • getEvents: use detail=7 for lifecycle fields such as creation and lastUpdate.
  • getEvents: use detail=8 for full payload with nested resources / additional.
  • getActivities: use detail=15 to include nested resources in activities.

Notes:

  • getEvents(detail>4) now auto-falls back to detail=4 when ADE returns only empty <event/> nodes.
  • Flattened rows now keep nested children (resources, additional, rights, etc.), including single-item responses.

Samples

  • php sample/events_detail_probe.php [activityId] [projectId] Compare getEvents detail 4, 7 and 8, print diagnostics and lifecycle/resource fields.
  • php sample/events_cache_fallback_probe.php [activityId] [projectId] Simulate a degenerate high-detail cache payload and show automatic fallback/recovery.
  • php sample/activities_detail_ladder_probe.php [codeX] [projectId] Demonstrate adaptive activity detail ladder (4 -> 10 -> 15) with required-field coverage.
  • php sample/object_search_playbook.php [projectId] [codeX] [--email=...] [--path=...] [--category=trainee] End-to-end object-search playbook with diagnostics for resources and activities.
  • php sample/events_by_email_and_codex.php <email> [codeX] [projectId] Fetch instructor events with optional codeX.
  • php sample/events_by_email_and_codex_monthly.php <email> [projectId] [--codex=ILL] [--sessions] [--json] Monthly aggregation/reporting sample (--sessions also prints creation / lastUpdate).

Caching

The API supports file-based caching:

$api->setCacheDir('/tmp/ade_cache', 172800, 21600);
// 172800s = 48h for resources/activities
// 21600s = 6h for events

Or use any PSR-16 compatible cache:

use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Psr16Cache;

$psr6Pool = new FilesystemAdapter('ade_api', 0, '/tmp/ade_cache');
$psr16Cache = new Psr16Cache($psr6Pool);
$api->setCache($psr16Cache);

Cache Policy, Fallback and Diagnostics

You can configure stale-cache fallback globally:

$api->setCachePolicy([
    'stale_on_error' => true,
    'stale_max_age_seconds' => 7 * 24 * 3600, // optional
]);

And override policy per call:

$resources = $api->getResources(
    ['category' => 'instructor', 'detail' => 6],
    true,
    ['stale_on_error' => false]
);

This also applies to tree=true lookups and hierarchy helpers:

$nodes = $api->useProject(4, fn (AdeWebAPI $api) => $api->getHierarchyFromPath(
    'Iut Illkirch.CHIMIE',
    [
        'resourceCategory' => 'trainee',
        'leafCategory' => 'trainee',
        'cachePolicy' => [
            'stale_on_error' => true,
            'stale_max_age_seconds' => 604800,
        ],
    ]
));

Fallback behavior details:

  • XML ADE errors (<error ...>) that correspond to server-side failures (for example 500 Internal Server Error) are treated as fallbackable when stale_on_error=true.
  • findResourceByPath* first tries stale cache with the exact segment query key (name, fatherIds, detail, category), then falls back to broader cached category snapshots.
  • getEvents fallback also handles degenerate high-detail payloads ([[], [], ...]) by retrying with detail=4, then overwriting the high-detail cache key with recovered data.
  • queryActivities now supports adaptive detail ladders to limit cost and escalate only when needed:
$activities = $api->queryActivities(
    ['codeX' => 'ILL'],
    15,
    [
        'prefer_lower_detail' => true,                // try 4 then 10 before 15
        'required_fields' => ['code', 'CODE'],        // escalate if missing
        'min_required_fields_coverage' => 0.85,       // expected row coverage
        // optional: 'detail_ladder' => [4, 10, 15],  // explicit order
    ]
);

After a call, diagnostics are available:

$diagnostic = $api->getLastCallDiagnostic();
// [
//   'source' => 'network' | 'cache_fresh' | 'cache_stale',
//   'fallbackReason' => 'http_500' | 'timeout' | 'parse_error' | ...
//   'cache' => ['key' => ..., 'expiresAt' => ..., 'isExpired' => ...]
// ]

Cache metadata can include expired entries:

$meta = $api->getCacheEntryMetadata(
    'getResources',
    ['category' => 'instructor', 'detail' => 6],
    true // includeExpired
);

Efficient Object Search

When ADE datasets are large, performance mainly depends on query order and detail levels. Use this playbook to keep requests fast and only pay for high-detail payloads when required.

1. Start With Narrow Resource Lookups

Prefer precise lookups before broad lists:

$instructor = $api->findResourceByField('instructor', 'email', $email, 10);
$group = $api->findResourceByPathInCategory('Iut Illkirch.CHIMIE', 'trainee', 2);
  • detail=2 is usually enough for path/category traversal.
  • detail=10 is useful when matching on fields like email.

2. Use Adaptive Detail For Activities

Activities can be expensive at high detail. Use the ladder:

$activities = $api->queryActivities(
    ['codeX' => 'ILL'],
    15,
    [
        'prefer_lower_detail' => true,
        'detail_ladder' => [4, 10, 15],
        'required_fields' => ['code', 'CODE'],
        'min_required_fields_coverage' => 0.85,
    ]
);
  • Fast path: keep detail 4 or 10 if coverage is already good.
  • Escalation path: move to 15 only if required fields are missing.

3. Query Events Only For Selected Activities

Build event queries from resolved activity IDs (not broad time windows when avoidable):

$eventRows = $api->queryEvents([
    'activities' => implode('|', $activityIds),
], 7);

Use detail=8 only if you also need nested children (resources, additional). getEvents already contains native fallback for degenerate high-detail payloads.

4. Keep Cache And Diagnostics On

$api->setCacheDir('/tmp/ade_cache', 172800, 21600);
$api->setCachePolicy([
    'stale_on_error' => true,
    'stale_max_age_seconds' => 7 * 24 * 3600,
]);

After each step, inspect diagnostics:

$diagnostic = $api->getLastCallDiagnostic();

Useful indicators:

  • source=cache_fresh|cache_stale|network
  • fallbackReason=detail_ladder_to_10|detail_fallback_to_4|http_500|...
  • requestedDetail / detailUsed (for queryActivities)

5. Typical Efficient Sequence

  1. Resolve a resource (findResourceByField or findResourceByPathInCategory)
  2. Fetch activities with adaptive ladder (queryActivities)
  3. Fetch events for selected activity IDs (queryEvents)
  4. Check diagnostics and tune ladder thresholds

Symfony Integration

1. Install

composer require blindaue/phpade

2. Configure (.env)

ADE_WEB_API_URL=https://ade.domain.tld/jsp/webapi
ADE_WEB_API_LOGIN=your_login
ADE_WEB_API_PASSWORD=your_password

3. Register Services

config/services.yaml:

services:
    Phpade\Config:
        public: true

    ade.cache.pool:
        class: Symfony\Component\Cache\Adapter\FilesystemAdapter
        arguments:
            - 'ade_api'
            - 0
            - '%kernel.cache_dir%/ade_api'

    ade.cache.psr16:
        class: Symfony\Component\Cache\Psr16Cache
        arguments:
            - '@ade.cache.pool'

    Phpade\AdeWebAPI:
        arguments:
            $config: '@Phpade\Config'
        calls:
            - method: setCache
              arguments:
                  - '@ade.cache.psr16'

Or with a framework cache pool:

framework:
    cache:
        pools:
            cache.app.ade_api:
                adapter: cache.adapter.filesystem
                default_lifetime: 86400

services:
    Phpade\Config:
        public: true

    ade.cache.psr16:
        class: Symfony\Component\Cache\Psr16Cache
        arguments:
            - '@cache.app.ade_api'

    Phpade\AdeWebAPI:
        arguments:
            $config: '@Phpade\Config'
        calls:
            - method: setCache
              arguments:
                  - '@ade.cache.psr16'

4. Usage in a Controller

<?php

namespace App\Controller;

use Phpade\AdeWebAPI;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class ScheduleController extends AbstractController
{
    #[Route('/instructors', name: 'instructors')]
    public function listInstructors(AdeWebAPI $api): Response
    {
        $api->connect();
        $api->setProject(4);

        $instructors = $api->getResources([
            'category' => 'instructor',
            'detail' => 6
        ]);

        $api->disconnect();

        return $this->render('instructors.html.twig', [
            'instructors' => $instructors
        ]);
    }
}

API Reference

See adesoft_webapi_doc.txt for complete API documentation.

Main Methods

MethodDescription
connect()Connect to the API
disconnect()Disconnect from the API
setCachePolicy([...])Configure global cache fallback policy
getLastCallDiagnostic()Get last call source/fallback/cache diagnostic
getCacheEntryMetadata($function, $params, $includeExpired = false)Get cache metadata (optionally including expired entry)
getProjects(['detail' => 4], $useCache = true, $cachePolicy = [])Get available projects
setProject($projectId)Set the current project
getResources([...], $useCache = true, $cachePolicy = [])Get resources (instructors, rooms, etc.)
findResourceByPath(..., $cachePolicy = [])Resolve a path with native stale-cache fallback
getActivities([...], $useCache = true, $cachePolicy = [])Get activities
getEvents([...], $useCache = true, $cachePolicy = [])Get events (includes nested child nodes such as resources)
getEventsByInstructorEmail($email, $codeX = null, $eventDetail = 4)High-level lookup by instructor email with configurable event detail
getHierarchyFromPath($path, ['cachePolicy' => ...])High-level hierarchy lookup with fallback policy support
queryActivities($filters, $detail, $options)Adaptive activity detail ladder (prefer_lower_detail, required_fields, detail_ladder)
queryEvents($filters, $detail = 4)Events query helper (use detail=7 for creation/lastUpdate)

Error Handling

Transport exceptions are enriched with:

  • getHttpStatus()
  • getAdeFunction()
  • getFailureReason() (for example: http_500, timeout, parse_error, transport_error)

Testing

vendor/bin/phpunit tests/php/
vendor/bin/phpcs --standard=PSR12 src/

Integration test is opt-in:

ADE_RUN_INTEGRATION_TESTS=1 ADE_TEST_PROJECT_ID=4 vendor/bin/phpunit tests/php/AdeWebAPITest.php