blindaue / phpade
PHP 8 ADE Web API Client
Requires
- php: >=8.0
- ext-simplexml: *
- guzzlehttp/guzzle: ^7.8
- psr/simple-cache: ^3.0
- vlucas/phpdotenv: ^5.6
Requires (Dev)
- phpunit/phpunit: ^9.6
- squizlabs/php_codesniffer: ^4.0
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: usedetail=4for base event data.getEvents: usedetail=7for lifecycle fields such ascreationandlastUpdate.getEvents: usedetail=8for full payload with nestedresources/additional.getActivities: usedetail=15to include nestedresourcesin activities.
Notes:
getEvents(detail>4)now auto-falls back todetail=4when 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]ComparegetEventsdetail4,7and8, 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 optionalcodeX.php sample/events_by_email_and_codex_monthly.php <email> [projectId] [--codex=ILL] [--sessions] [--json]Monthly aggregation/reporting sample (--sessionsalso printscreation/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 example500 Internal Server Error) are treated as fallbackable whenstale_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.getEventsfallback also handles degenerate high-detail payloads ([[], [], ...]) by retrying withdetail=4, then overwriting the high-detail cache key with recovered data.queryActivitiesnow 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=2is usually enough for path/category traversal.detail=10is useful when matching on fields likeemail.
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
4or10if coverage is already good. - Escalation path: move to
15only 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|networkfallbackReason=detail_ladder_to_10|detail_fallback_to_4|http_500|...requestedDetail/detailUsed(forqueryActivities)
5. Typical Efficient Sequence
- Resolve a resource (
findResourceByFieldorfindResourceByPathInCategory) - Fetch activities with adaptive ladder (
queryActivities) - Fetch events for selected activity IDs (
queryEvents) - 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
| Method | Description |
|---|---|
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