dockcodes / dockaccess-alt-generator-symfony
Symfony bundle for the dockaccess.org AI alt text generator: connection to the account, a service, a validation constraint, a Messenger handler and console commands.
Package info
github.com/dockcodes/dockaccess-ai-alt-generator-symfony
Type:symfony-bundle
pkg:composer/dockcodes/dockaccess-alt-generator-symfony
Requires
- php: ^8.2
- dockcodes/dockaccess-alt-generator: ^1.1
- psr/cache: ^2.0|^3.0
- psr/log: ^2.0|^3.0
- symfony/config: ^6.4|^7.4|^8.0
- symfony/console: ^6.4|^7.4|^8.0
- symfony/dependency-injection: ^6.4|^7.4|^8.0
- symfony/event-dispatcher: ^6.4|^7.4|^8.0
- symfony/http-foundation: ^6.4|^7.4|^8.0
- symfony/http-kernel: ^6.4|^7.4|^8.0
Requires (Dev)
- league/flysystem: ^3.0
- league/flysystem-memory: ^3.0
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.0|^12.0
- symfony/framework-bundle: ^6.4|^7.4|^8.0
- symfony/messenger: ^6.4|^7.4|^8.0
- symfony/translation: ^6.4|^7.4|^8.0
- symfony/validator: ^6.4|^7.4|^8.0
Suggests
- ext-gd: Re-encodes every image before upload (pixels only: no metadata, no appended data) and scales it down.
- league/flysystem: Generate alt text for files on any Flysystem storage (S3, ...)
- symfony/messenger: Generate alt text in the background (GenerateAltText message)
- symfony/validator: The GenuineImage constraint for uploads
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-28 13:10:27 UTC
README
Alt text for your images from the dockaccess.org AI alt generator, in a Symfony application: a connection to your dockaccess.org account, a service, a validation constraint, a Messenger handler and console commands.
It is built on the PHP core, so it inherits what the core guarantees:
- only genuine images leave the server. Every file is identified from its content and rebuilt
before it is sent. A
shell.phprenamed tophoto.png, PHP hidden in a JPEG comment, an SVG with a script or a decompression bomb is refused locally. - no metadata leaks. GPS positions, camera serials and author names stay on the server.
- the account key does not leak into exceptions, logs, dumps or serialized messages.
Requirements
- PHP 8.2 or newer, 64-bit, with
ext-curlandext-json - Symfony 6.4, 7.4 or 8
ext-gdwith JPEG, PNG, GIF and WebP support, strongly recommended (see the core's README)- a dockaccess.org account with the alt generator switched on for the site (the free plan works: 1 site, 10 alts a day)
Installation
composer require dockcodes/dockaccess-alt-generator-symfony bin/console dockaccess:alt:connect
Symfony Flex registers the bundle; without Flex add it to config/bundles.php:
Dock\AltGenerator\Symfony\DockAccessAltGeneratorBundle::class => ['all' => true],
dockaccess:alt:connect asks for:
- the site token - in your dockaccess.org account, open the site, then the ALT generator tab;
- the e-mail address and the password of the account. The password is used once and is not stored.
It connects this application as symfony, the way the WordPress, PrestaShop and Joomla plugins
connect, and writes three values to .env.local (created readable by its owner only):
DOCKACCESS_ALT_KEY=... DOCKACCESS_ALT_SITE_TOKEN=... DOCKACCESS_ALT_SECRET=...
The address of the application (framework.router.default_uri, or --url) has to be one of the
domains of the site in the account. A local address (localhost, a private IP) is accepted too,
for development.
The connected application then appears in the account, in the ALT generator tab of the site, with the time it last checked in. The owner can disconnect it there.
Run bin/console dockaccess:alt:status to see the connection and how many alts are left.
For production, keep the three values in real environment variables or in the secrets vault
(bin/console secrets:set DOCKACCESS_ALT_KEY) - --show prints them instead of writing
.env.local. In CI, pass --no-interaction with --token, --login and the password in the
DOCKACCESS_PASSWORD environment variable.
Generating
Inject Dock\AltGenerator\Symfony\AltText:
use Dock\AltGenerator\Symfony\AltText; public function upload(Request $request, AltText $altText): Response { $alt = $altText->generate($request->files->get('photo'))->alt()->text(); // ... }
// A file on a Flysystem storage (league/flysystem-bundle): local, S3, ... $alt = $altText->generateFromFilesystem($defaultStorage, 'products/42.jpg')->alt()->text(); // With options $result = $altText->generate($file, [ 'language' => 'de_AT', // default: the request locale, then kernel.default_locale 'tone' => 'descriptive', // Dock\AltGenerator\Tone::ALL 'context' => 'Product: oak dining table, 180 cm', 'variants' => 3, // 1 to 3 alternatives ]);
The alt is already plain text (no markup, control characters or bidirectional overrides), but it is text from outside: escape it wherever it is printed, as Twig does.
Absolute paths are read only inside allowed_directories (default %kernel.project_dir%/public/uploads).
In the background (Messenger)
use Dock\AltGenerator\Symfony\Message\GenerateAltText; $bus->dispatch(new GenerateAltText('products/42.jpg', 'default.storage', ['context' => $product->getName()], ['product_id' => $product->getId()]));
# config/packages/messenger.yaml framework: messenger: routing: Dock\AltGenerator\Symfony\Message\GenerateAltText: async
The second argument is the service id of a Flysystem storage; storages tagged flysystem.storage
are known to the bundle, others can be listed in dockaccess_alt_generator.storages. A message
cannot reach any other service. With null the path is an absolute one inside allowed_directories.
Listen for the result:
use Dock\AltGenerator\Symfony\Event\AltTextGeneratedEvent; use Dock\AltGenerator\Symfony\Event\AltTextFailedEvent; #[AsEventListener] public function onGenerated(AltTextGeneratedEvent $event): void { $this->products->find($event->context['product_id'])?->setImageAlt($event->alt); $this->entityManager->flush(); } #[AsEventListener] public function onFailed(AltTextFailedEvent $event): void { $this->logger->notice('No alt for '.$event->path.': '.$event->reason); }
Only what can succeed later goes back to Messenger's retry: the per minute cap (after the delay
the generator asks for, on Symfony 7.2+) and outages. A spent limit, a refused image or a
disconnected application are unrecoverable. AltTextFailedEvent comes once Messenger gives up.
Validating uploads
use Dock\AltGenerator\Symfony\Validator\GenuineImage; #[Assert\NotNull, GenuineImage] public ?UploadedFile $photo = null;
The constraint runs the core's inspection: the file is checked by its content, not by its
extension or MIME type. No connection is needed and nothing is sent. Messages are in English and
Polish (translations/validators.*.php, domain validators).
Limits
The free plan counts alts per UTC day, paid plans per UTC calendar month (a month's alts can go in one day).
$usage = $altText->usage(); $usage->used(); // 15 $usage->limit(); // 2000 $usage->remaining(); // 1985 $usage->isMonthly(); // true on a paid plan $usage->resetsAt(); // DateTimeImmutable, UTC
A spent limit throws DailyLimitReachedException (the name predates monthly plans;
getPeriod() says day or month).
The connection check
Before generating, the bundle asks dockaccess.org whether the generator is still on for the site
and remembers the answer for 5 minutes (verify.ttl, in cache.app). Generation stops with
GeneratorUnavailableException when:
getReason() |
What happened | What to do |
|---|---|---|
disconnected |
The owner disconnected the application in the account | bin/console dockaccess:alt:connect |
alt_disabled |
The generator was switched off for the site | Switch it on in the account |
alt_paused |
The plan paused it (e.g. more sites than the plan allows) | Upgrade the plan |
When dockaccess.org cannot be reached, generation goes on (a warning is logged on the
dockaccess_alt channel): the generator itself still enforces the account's limits.
Errors
Every exception implements Dock\AltGenerator\Exception\AltGeneratorException.
| Exception | Meaning |
|---|---|
InvalidImageException |
Refused locally, nothing was sent; getReason() says why |
GeneratorUnavailableException |
The account says no (see above), nothing was sent |
ConfigurationException |
Not connected, or a setting is invalid |
AuthenticationException |
The account key was not accepted - connect again |
DailyLimitReachedException |
The day's (free) or month's (paid) limit is spent |
RateLimitedException |
Too many requests per minute; getRetryAfter() |
ImageRejectedException |
The generator refused the image |
ServerException, TransportException |
Try again later |
Commands
| Command | |
|---|---|
dockaccess:alt:connect |
Connect the application and write the keys to .env.local (--overwrite replaces another installation's connection, --show prints the keys) |
dockaccess:alt:status |
The connection, the sites with the generator and the usage |
dockaccess:alt:generate {path} |
One generation, to try the setup |
dockaccess:alt:disconnect |
Disconnect and remove the keys from .env.local |
Configuration
Everything has a default; bin/console config:dump-reference dockaccess_alt_generator shows it all.
# config/packages/dockaccess_alt_generator.yaml dockaccess_alt_generator: defaults: language: ~ # the request locale tone: neutral set_ref: ~ # the site token allowed_directories: - '%kernel.project_dir%/public/uploads' image: mode: auto # reencode sends only GD-written bytes verify: enabled: true ttl: 300 http: proxy: '%env(default::DOCKACCESS_ALT_PROXY)%'
Testing your application
Replace the transport in the test environment; images are still inspected as in production.
# config/services_test.yaml services: Dock\AltGenerator\Http\TransportInterface: class: Dock\AltGenerator\Symfony\Testing\FakeTransport factory: [Dock\AltGenerator\Symfony\Testing\FakeTransport, alts] arguments: ['A red cat on a windowsill'] public: true
$transport = static::getContainer()->get(TransportInterface::class); $transport->assertGenerated(1);
$transport->respond('/api/generate', FakeTransport::json([...], 429, [...])) simulates a spent
limit or another refusal; respond('/integrations/verify', ...) a disconnected site.
Development
composer install
composer test
composer analyse
License
MIT, see LICENSE.