ice/minervaclient-bundle
Symfony bundle that integrates the Minerva client into your app, with configurable service wiring, authentication, and HTTP transport for calling Minerva APIs with minimal setup. It centralizes connection settings, supports dependency injection, and keeps API usage consistent across environments.
This is a thin Symfony bundle wrapping an HTTP client for the ICE Minerva API, published by Cambridge University. With zero stars and zero dependents it is effectively an internal/legacy integration, so treat your first hour as reconnaissance rather than following a README.
composer require ice/minervaclient-bundle — then immediately edit composer.json to pin an exact version ("ice/minervaclient-bundle": "1.2.3"). Given the adoption profile there will be no deprecation runway if a release breaks you.config/bundles.php add the bundle class (find it under vendor/ice/minervaclient-bundle/src/). On Symfony 3.x this is AppKernel::registerBundles() instead.DependencyInjection/Configuration.php and the extension's getAlias(). The alias is usually derived from the bundle name (minerva_client, ice_minerva_client, …) but guessing wastes time. Confirm with:
bin/console debug:config <alias>
bin/console debug:container --tag=... | grep -i minerva
config/packages/<alias>.yaml, sourcing secrets from env vars (%env(MINERVA_API_KEY)%), never committed literals.dump() the raw response. This tells you the wire format (JSON vs XML), the ID conventions, and the error shape before you design anything around it.If debug:container shows no Minerva services, autowiring is probably disabled — define the service explicitly in services.yaml with an arguments: block rather than fighting the bundle.
Wrap the client in your own gateway. Never let Minerva types leak into controllers or domain services. Create App\Minerva\MinervaGateway that takes the bundle's client as a constructor argument and exposes methods in your vocabulary (findAssessment(), listResources()). When the bundle is abandoned — plan for it — you swap one class, not fifty call sites.
final class MinervaGateway
{
public function __construct(private MinervaClient $client) {}
public function fetchItem(string $id): ?MinervaItem
{
try {
$raw = $this->client->get($id);
} catch (MinervaException $e) {
throw new ItemUnavailable($id, previous: $e);
}
return MinervaItem::fromPayload($raw);
}
}
Normalise into DTOs at the boundary. Map the raw payload into immutable value objects with a static fromPayload(). This is where you absorb inconsistencies in field naming, nullability, and date formats so they never reach your templates.
Cache aggressively, keyed by resource. Minerva data is reference-like and changes rarely relative to reads. Use Symfony Cache Contracts inside the gateway with keys like minerva.item.{id}.v1; bump v1 when your mapping changes, not when the upstream does.
Handle retries and timeouts at the transport layer. Symfony HttpClient supports retry_http_codes/max_duration in the framework config; if the bundle uses Guzzle, add a Middleware::retry() handler and pass it into the client's handler stack via a service decorator. Never rely on the bundle's defaults for production timeouts — assume they are unset.
Testing. Because the bundle exposes a concrete client, isolate it behind your gateway interface and in tests bind a fake implementation. For integration tests against the bundle itself, use MockHttpClient with queued responses (Symfony) or Guzzle's MockHandler. Write one contract test asserting your gateway handles a 401, a 404, and a malformed payload.
Async where it matters. If you sync batches of records, drive them from a console command (app:minerva:sync) and dispatch per-item messages to a queue. One slow upstream call should not stall a request.
composer why-not symfony/framework-bundle before anything else; if it conflicts, you are forking, not installing.symfony/http-client have completely different mocking, retry, and logging stories. Read composer.json in vendor/ice/minervaclient-bundle/ — it is the most honest documentation you will get.getAlias().config/services_test.yaml; don't reach into $container->get().// or drop a path segment. If every call 404s, log the fully resolved URI before blaming credentials.int in PHP on 32-bit boundaries and lose leading zeros. Keep them as strings end-to-end.monolog: channels: [minerva] plus a request/response logger saves hours during upstream incidents, and gives you a way to redact credentials before they hit disk.symfony/http-client; with Guzzle you get nothing for free — add a Middleware::log().services: App\Minerva\LoggingClient: decorates: <original service id>.HttpClient call in an afternoon if the package stops being maintained.How can I help you explore Laravel packages today?