PluginProbe
Auto Alt Text / trunk
Auto Alt Text vtrunk
3.0.3 2.8.2 1.3.1 1.3.2 2.0.0 2.1.0 2.1.1 2.2.0 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.4.0 2.4.1 2.4.2 2.5.0 2.5.1 2.5.2 2.5.3 2.6.0 2.6.1 2.7.0 2.8.0 2.8.1 All 28 releases
auto-alt-text / src / App / Core / Container.php

Container.php in Auto Alt Text trunk, at src/App/Core/Container.php

481 lines 19.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace AATXT\App\Core;
6
7 use AATXT\App\Admin\MediaLibrary;
8 use AATXT\App\Admin\PluginOptions;
9 use AATXT\App\AIProviders\Anthropic\AnthropicModelsRegistry;
10 use AATXT\App\AIProviders\Anthropic\AnthropicResponse;
11 use AATXT\App\AIProviders\Azure\AzureComputerVisionCaptionsResponse;
12 use AATXT\App\AIProviders\Azure\AzureTranslator;
13 use AATXT\App\AIProviders\Gemini\GeminiModelsRegistry;
14 use AATXT\App\AIProviders\Gemini\GeminiResponse;
15 use AATXT\App\AIProviders\OpenAI\OpenAIModelsRegistry;
16 use AATXT\App\AIProviders\OpenAI\OpenAIVision;
17 use AATXT\App\Configuration\AnthropicConfig;
18 use AATXT\App\Configuration\AzureConfig;
19 use AATXT\App\Configuration\GeminiConfig;
20 use AATXT\App\Configuration\OpenAIConfig;
21 use AATXT\App\Infrastructure\Cache\CacheInterface;
22 use AATXT\App\Infrastructure\Cache\WordPressTransientCache;
23 use AATXT\App\Infrastructure\Database\ErrorLogSchema;
24 use AATXT\App\Infrastructure\Http\HttpClientInterface;
25 use AATXT\App\Infrastructure\Http\ImageFetcherInterface;
26 use AATXT\App\Infrastructure\Http\WordPressHttpClient;
27 use AATXT\App\Infrastructure\Http\WordPressImageFetcher;
28 use AATXT\App\Infrastructure\Repositories\ConfigRepositoryInterface;
29 use AATXT\App\Infrastructure\Repositories\ErrorLogRepository;
30 use AATXT\App\Infrastructure\Repositories\ErrorLogRepositoryInterface;
31 use AATXT\App\Infrastructure\Repositories\WordPressConfigRepository;
32 use AATXT\App\Logging\DBLogger;
33 use AATXT\App\Services\AltTextGeneratorFactory;
34 use AATXT\App\Services\ConfigBasedGeneratorFactory;
35 use AATXT\App\Services\AltTextService;
36 use AATXT\App\Services\ContentAltTextEnricher;
37 use AATXT\App\Utilities\AssetsManager;
38 use AATXT\App\AIProviders\Decorators\DecoratorBuilder;
39 use AATXT\App\AltTextGeneratorAi;
40 use AATXT\App\AltTextGeneratorAttachmentTitle;
41 use AATXT\App\AltTextGeneratorParentPostTitle;
42 use AATXT\App\Events\EventDispatcherInterface;
43 use AATXT\App\Events\SimpleEventDispatcher;
44 use AATXT\App\Events\AltTextGenerationFailedEvent;
45 use AATXT\App\Events\Listeners\LogErrorListener;
46 use AATXT\App\Events\Listeners\NotifyAdminListener;
47 use AATXT\Config\Constants;
48 use AATXT\Vendor\DI\Container as DIContainer;
49 use AATXT\Vendor\DI\ContainerBuilder;
50
51 /**
52 * Dependency Injection Container configuration.
53 *
54 * This class sets up and configures the PHP-DI container with all
55 * service bindings for the plugin. It implements the Dependency Inversion
56 * Principle by binding interfaces to concrete implementations.
57 *
58 * Usage:
59 * ```php
60 * $container = Container::make();
61 * $service = $container->get(SomeService::class);
62 * ```
63 */
64 final class Container
65 {
66 private static ?DIContainer $instance = null;
67
68 /**
69 * Private constructor to prevent direct instantiation.
70 */
71 private function __construct()
72 {
73 }
74
75 /**
76 * Get or create the container instance.
77 *
78 * @return DIContainer The configured container
79 * @throws \Exception If container build fails
80 */
81 public static function make(): DIContainer
82 {
83 if (self::$instance === null) {
84 self::$instance = self::build();
85 }
86
87 return self::$instance;
88 }
89
90 /**
91 * Build and configure the container with all service bindings.
92 *
93 * @return DIContainer The configured container
94 * @throws \Exception If container build fails
95 */
96 private static function build(): DIContainer
97 {
98 $builder = new ContainerBuilder();
99
100 // Enable compilation for better performance in production
101 // Note: Disable in development if you need to modify bindings frequently
102 // $builder->enableCompilation(__DIR__ . '/../../../var/cache');
103
104 $builder->addDefinitions(self::getDefinitions());
105
106 return $builder->build();
107 }
108
109 /**
110 * Get all service definitions for the container.
111 *
112 * @return array<string, mixed> Array of service definitions
113 */
114 private static function getDefinitions(): array
115 {
116 return [
117 // WordPress database abstraction
118 // Maps wpdb class to the global WordPress database object
119 \wpdb::class => function () {
120 return $GLOBALS['wpdb'];
121 },
122
123 // Database Schema Management
124 // Manages error logs table schema
125 ErrorLogSchema::class => \AATXT\Vendor\DI\create(ErrorLogSchema::class)
126 ->constructor(\AATXT\Vendor\DI\get(\wpdb::class)),
127
128 // Error Log Repository
129 // Maps interface to concrete implementation for error log persistence
130 ErrorLogRepositoryInterface::class => \AATXT\Vendor\DI\create(ErrorLogRepository::class)
131 ->constructor(
132 \AATXT\Vendor\DI\get(\wpdb::class),
133 \AATXT\Vendor\DI\get(ErrorLogSchema::class)
134 ),
135
136 // Config Repository
137 // Maps interface to WordPress options implementation for configuration management
138 ConfigRepositoryInterface::class => \AATXT\Vendor\DI\create(WordPressConfigRepository::class),
139
140 // Database Logger
141 // Legacy logger refactored to use repository pattern
142 DBLogger::class => \AATXT\Vendor\DI\create(DBLogger::class)
143 ->constructor(
144 \AATXT\Vendor\DI\get(ErrorLogRepositoryInterface::class),
145 \AATXT\Vendor\DI\get(ErrorLogSchema::class)
146 ),
147
148 // HTTP Client abstraction
149 // Maps HttpClientInterface to WordPress HTTP client implementation
150 HttpClientInterface::class => \AATXT\Vendor\DI\create(WordPressHttpClient::class),
151
152 // Image fetcher abstraction
153 // Reads image bytes for providers that require inline (base64) image data
154 ImageFetcherInterface::class => \AATXT\Vendor\DI\create(WordPressImageFetcher::class),
155
156 // Cache abstraction
157 // Maps CacheInterface to the WordPress Transients API
158 CacheInterface::class => \AATXT\Vendor\DI\create(WordPressTransientCache::class),
159
160 // Anthropic Models Registry
161 // Fetches the available Claude models from the Anthropic API, with caching
162 AnthropicModelsRegistry::class => function ($container) {
163 return new AnthropicModelsRegistry(
164 $container->get(HttpClientInterface::class),
165 $container->get(CacheInterface::class),
166 PluginOptions::apiKeyAnthropic()
167 );
168 },
169
170 // OpenAI Models Registry
171 // Fetches the available OpenAI models from the OpenAI API, with caching
172 OpenAIModelsRegistry::class => function ($container) {
173 return new OpenAIModelsRegistry(
174 $container->get(HttpClientInterface::class),
175 $container->get(CacheInterface::class),
176 PluginOptions::apiKeyOpenAI()
177 );
178 },
179
180 // Gemini Models Registry
181 // Fetches the available Gemini models from the Google API, with caching
182 GeminiModelsRegistry::class => function ($container) {
183 return new GeminiModelsRegistry(
184 $container->get(HttpClientInterface::class),
185 $container->get(CacheInterface::class),
186 PluginOptions::apiKeyGemini()
187 );
188 },
189
190 // OpenAI Configuration
191 // Factory that reads configuration from WordPress options
192 OpenAIConfig::class => function () {
193 return new OpenAIConfig(
194 PluginOptions::apiKeyOpenAI(),
195 PluginOptions::openAiPrompt(),
196 PluginOptions::openAiModel()
197 );
198 },
199
200 // Anthropic Configuration
201 // Factory that reads configuration from WordPress options
202 AnthropicConfig::class => function () {
203 return new AnthropicConfig(
204 PluginOptions::apiKeyAnthropic(),
205 PluginOptions::anthropicPrompt(),
206 PluginOptions::anthropicModel()
207 );
208 },
209
210 // Gemini Configuration
211 // Factory that reads configuration from WordPress options
212 GeminiConfig::class => function () {
213 return new GeminiConfig(
214 PluginOptions::apiKeyGemini(),
215 PluginOptions::geminiPrompt(),
216 PluginOptions::geminiModel()
217 );
218 },
219
220 // Azure Configuration
221 // Factory that reads configuration from WordPress options
222 // Includes both Computer Vision and Translator settings
223 AzureConfig::class => function () {
224 return new AzureConfig(
225 PluginOptions::apiKeyAzureComputerVision(),
226 PluginOptions::endpointAzureComputerVision(),
227 '', // Azure doesn't use a model parameter
228 '', // Azure doesn't use a custom prompt
229 PluginOptions::apiKeyAzureTranslateInstance(),
230 PluginOptions::endpointAzureTranslateInstance(),
231 PluginOptions::regionAzureTranslateInstance(),
232 PluginOptions::languageAzureTranslateInstance()
233 );
234 },
235
236 // OpenAI Vision Provider
237 // Automatically injects HttpClientInterface, OpenAIConfig and the models registry
238 // (the registry powers the runtime fallback when the configured model is unavailable)
239 OpenAIVision::class => \AATXT\Vendor\DI\create(OpenAIVision::class)
240 ->constructor(
241 \AATXT\Vendor\DI\get(HttpClientInterface::class),
242 \AATXT\Vendor\DI\get(OpenAIConfig::class),
243 \AATXT\Vendor\DI\get(OpenAIModelsRegistry::class)
244 ),
245
246 // Anthropic Claude Provider
247 // Automatically injects HttpClientInterface, AnthropicConfig and the models registry
248 // (the registry powers the runtime fallback when the configured model is unavailable)
249 AnthropicResponse::class => \AATXT\Vendor\DI\create(AnthropicResponse::class)
250 ->constructor(
251 \AATXT\Vendor\DI\get(HttpClientInterface::class),
252 \AATXT\Vendor\DI\get(AnthropicConfig::class),
253 \AATXT\Vendor\DI\get(AnthropicModelsRegistry::class)
254 ),
255
256 // Google Gemini Provider
257 // Automatically injects HttpClientInterface, GeminiConfig, the image fetcher
258 // (the Interactions API only accepts inline base64 image data) and the models
259 // registry (which powers the runtime fallback when the configured model is unavailable)
260 GeminiResponse::class => \AATXT\Vendor\DI\create(GeminiResponse::class)
261 ->constructor(
262 \AATXT\Vendor\DI\get(HttpClientInterface::class),
263 \AATXT\Vendor\DI\get(GeminiConfig::class),
264 \AATXT\Vendor\DI\get(ImageFetcherInterface::class),
265 \AATXT\Vendor\DI\get(GeminiModelsRegistry::class)
266 ),
267
268 // Azure Translator
269 // Automatically injects HttpClientInterface and AzureConfig
270 AzureTranslator::class => \AATXT\Vendor\DI\create(AzureTranslator::class)
271 ->constructor(
272 \AATXT\Vendor\DI\get(HttpClientInterface::class),
273 \AATXT\Vendor\DI\get(AzureConfig::class)
274 ),
275
276 // Azure Computer Vision Provider
277 // Automatically injects HttpClientInterface, AzureConfig, and AzureTranslator
278 AzureComputerVisionCaptionsResponse::class => \AATXT\Vendor\DI\create(AzureComputerVisionCaptionsResponse::class)
279 ->constructor(
280 \AATXT\Vendor\DI\get(HttpClientInterface::class),
281 \AATXT\Vendor\DI\get(AzureConfig::class),
282 \AATXT\Vendor\DI\get(AzureTranslator::class)
283 ),
284
285 // =============================================
286 // Decorated AI Providers (using Decorator Pattern)
287 // Order: Provider → Cleaning → Validation → Caching
288 // =============================================
289
290 // Decorated OpenAI Vision Provider
291 // Applies cleaning and validation decorators
292 'openai.vision.decorated' => function ($container) {
293 return DecoratorBuilder::wrap($container->get(OpenAIVision::class))
294 ->withCleaning()
295 ->withValidation(false)
296 ->build();
297 },
298
299 // Decorated Anthropic Provider
300 // Applies cleaning and validation decorators
301 'anthropic.decorated' => function ($container) {
302 return DecoratorBuilder::wrap($container->get(AnthropicResponse::class))
303 ->withCleaning()
304 ->withValidation(false)
305 ->build();
306 },
307
308 // Decorated Gemini Provider
309 // Applies cleaning and validation decorators
310 'gemini.decorated' => function ($container) {
311 return DecoratorBuilder::wrap($container->get(GeminiResponse::class))
312 ->withCleaning()
313 ->withValidation(false)
314 ->build();
315 },
316
317 // Decorated Azure Provider
318 // Applies cleaning and validation decorators
319 // Note: Azure has built-in translation, so cleaning is important
320 'azure.decorated' => function ($container) {
321 return DecoratorBuilder::wrap($container->get(AzureComputerVisionCaptionsResponse::class))
322 ->withCleaning()
323 ->withValidation(false)
324 ->build();
325 },
326
327 // Alt Text Generator Factory
328 // Factory pattern for creating different types of alt text generators
329 // Uses decorated providers for cleaning and validation
330 AltTextGeneratorFactory::class => function ($container) {
331 $factory = new ConfigBasedGeneratorFactory();
332
333 // Register OpenAI Vision generator (decorated)
334 $factory->register(
335 Constants::AATXT_OPTION_TYPOLOGY_CHOICE_OPENAI,
336 function () use ($container) {
337 return AltTextGeneratorAi::make(
338 $container->get('openai.vision.decorated')
339 );
340 }
341 );
342
343 // Register Anthropic generator (decorated)
344 $factory->register(
345 Constants::AATXT_OPTION_TYPOLOGY_CHOICE_ANTHROPIC,
346 function () use ($container) {
347 return AltTextGeneratorAi::make(
348 $container->get('anthropic.decorated')
349 );
350 }
351 );
352
353 // Register Gemini generator (decorated)
354 $factory->register(
355 Constants::AATXT_OPTION_TYPOLOGY_CHOICE_GEMINI,
356 function () use ($container) {
357 return AltTextGeneratorAi::make(
358 $container->get('gemini.decorated')
359 );
360 }
361 );
362
363 // Register Azure generator (decorated)
364 $factory->register(
365 Constants::AATXT_OPTION_TYPOLOGY_CHOICE_AZURE,
366 function () use ($container) {
367 return AltTextGeneratorAi::make(
368 $container->get('azure.decorated')
369 );
370 }
371 );
372
373 // Register Parent Post Title generator
374 $factory->register(
375 Constants::AATXT_OPTION_TYPOLOGY_CHOICE_ARTICLE_TITLE,
376 function () {
377 return AltTextGeneratorParentPostTitle::make();
378 }
379 );
380
381 // Register Attachment Title generator
382 $factory->register(
383 Constants::AATXT_OPTION_TYPOLOGY_CHOICE_ATTACHMENT_TITLE,
384 function () {
385 return AltTextGeneratorAttachmentTitle::make();
386 }
387 );
388
389 return $factory;
390 },
391
392
393 // =============================================
394 // Event System (Observer Pattern)
395 // =============================================
396
397 // Log Error Listener
398 // Listens for AltTextGenerationFailedEvent and logs errors to database
399 LogErrorListener::class => function ($container) {
400 return new LogErrorListener(
401 $container->get(ErrorLogRepositoryInterface::class)
402 );
403 },
404
405 // Notify Admin Listener
406 // Listens for failure events and can send email notifications
407 // Email notifications are disabled by default
408 NotifyAdminListener::class => function () {
409 return new NotifyAdminListener(
410 false, // Email disabled by default
411 5 // Threshold: 5 failures before notification
412 );
413 },
414
415 // Event Dispatcher
416 // Central event dispatcher with pre-registered listeners
417 EventDispatcherInterface::class => function ($container) {
418 $dispatcher = new SimpleEventDispatcher();
419
420 // Register LogErrorListener for failure events
421 // Note: We're using the listener via event system instead of direct logging
422 // This allows for decoupled error handling
423 $logErrorListener = $container->get(LogErrorListener::class);
424 $dispatcher->listen(
425 AltTextGenerationFailedEvent::class,
426 [$logErrorListener, 'handle']
427 );
428
429 // Register NotifyAdminListener for failure events
430 $notifyAdminListener = $container->get(NotifyAdminListener::class);
431 $dispatcher->listen(
432 AltTextGenerationFailedEvent::class,
433 [$notifyAdminListener, 'handleFailure']
434 );
435
436 return $dispatcher;
437 },
438
439 // Alt Text Service
440 // Main service for generating alt text, uses factory and handles errors
441 // Integrated with Event System for decoupled logging
442 AltTextService::class => function ($container) {
443 return new AltTextService(
444 $container->get(AltTextGeneratorFactory::class),
445 $container->get(ConfigRepositoryInterface::class),
446 $container->get(ErrorLogRepositoryInterface::class),
447 $container->get(EventDispatcherInterface::class)
448 );
449 },
450
451 // Content Alt Text Enricher
452 // Fills missing alt text on <img> tags in rendered post content
453 // by resolving each image against the media library (the_content filter)
454 ContentAltTextEnricher::class => \AATXT\Vendor\DI\create(ContentAltTextEnricher::class),
455
456 // Assets Manager
457 // Handles Vite manifest loading for versioned assets
458 AssetsManager::class => \AATXT\Vendor\DI\create(AssetsManager::class),
459
460 // Media Library
461 // Handles media library UI customization and AJAX alt text generation
462 MediaLibrary::class => function ($container) {
463 return new MediaLibrary(
464 $container->get(AltTextService::class),
465 $container->get(AssetsManager::class)
466 );
467 },
468 ];
469 }
470
471 /**
472 * Reset the container instance (useful for testing).
473 *
474 * @return void
475 */
476 public static function reset(): void
477 {
478 self::$instance = null;
479 }
480 }
481