Introduction to the Image Generator Service with Symfony
Welcome to the third lesson of our course on building an image generation service with Symfony! In our previous lessons, we created the PromptManager to format user inputs into detailed prompts and the ImageManager to handle storing and processing generated images. Now, we're ready to build the core component that brings everything together: the ImageGeneratorService.
The ImageGeneratorService is the central piece of our application that will:
Connect to Google's Gemini image generation API to generate images.
Use our PromptManager to format user inputs into effective prompts.
Store generated images using our ImageManager.
Provide access to all previously generated images.
This service acts as the bridge between our application's components and the external AI service that actually creates the images. By encapsulating all the image generation logic in a dedicated service class, we maintain a clean separation of concerns in our application architecture.
In this lesson, we'll implement this service step by step, from setting up the API client to handling responses and errors. By the end, you'll have a fully functional image generation service that you can later integrate into a Symfony web application.
Setting Up the Image Generator Service
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal
Before we can generate images, we need to set up a service to communicate with Google's Gemini image generation API. This API provides access to Google's image generation model (gemini-3.1-flash-image), allowing us to create high-quality images from text prompts.
First, we need to install Symfony's HTTP Client component to make API requests. In a typical development environment, you would run:
Shell
composer require symfony/http-client
Now, let's create our ImageGeneratorService class in the src/Service directory:
In this constructor, we're doing several important things:
Injecting our PromptManager service to handle formatting prompts, and assigning it to $this->promptManager so the rest of the service can use it.
Injecting our ImageManager service to handle storing and retrieving images.
Injecting Symfony's HttpClientInterface to make API requests.
Injecting the ParameterBagInterface to access configuration parameters.
Retrieving the API key and base URL from environment variables.
For security reasons, it's best to store API keys in environment variables rather than hardcoding them in your source code. In Symfony, we can define this in our .env file:
This way, our API key is securely stored and can be easily changed between environments.
Implementing the Image Generation Logic
Error Handling and Service Integration
`ImageGeneratorService` Complete Implementation
Testing the Complete Service
Summary and Practice Preview
In this lesson, we've built the ImageGeneratorService, the core component of our image generation application. This service connects our previously built components (PromptManager and ImageManager) to Google's Gemini image generation API, allowing us to generate high-quality images from text prompts.
Let's review what we've learned:
We set up a Symfony service to communicate with Google's Gemini image generation API.
We implemented the generateImage() method to create images from user inputs.
We added robust error handling using Symfony's exception system.
We created a getImages() method to retrieve all previously generated images.
We tested our service with a Symfony command.
The ImageGeneratorService is a crucial piece of our application architecture. It encapsulates all the logic related to image generation, providing a clean interface for other components to use. In the next lesson, we'll build a controller that will use this service to handle HTTP requests in our Symfony application.
Remember that to use this service in a real application, you'll need to:
Install Symfony's HTTP Client component.
Obtain a valid API key from Google.
Configure the API key in your environment variables.
Register the service in your Symfony service container.
With the ImageGeneratorService in place, we're one step closer to having a complete image generation web application!
Now that we have our service set up, let's implement the core method: generateImage(). This method will take a user input string, format it into a detailed prompt using our PromptManager, send the request to the Gemini image generation API, and store the resulting image using our ImageManager.
We start by calling formatPrompt() to convert the user's input into a detailed prompt using our predefined template.
We then construct the URL for the Gemini image generation API, including our API key as a query parameter. Gemini supports passing the API key as a ?key= query parameter, which is what we use here for the demo. Be aware that URLs are commonly logged by servers and proxies; where an endpoint supports it, a header-based credential is preferable.
We make the API call using Symfony's HTTP client, which takes several parameters:
Headers: We specify that we're sending JSON data.
JSON payload: We wrap the prompt in a contents array and include a generationConfig requesting both text and image response modalities, along with image configuration such as the aspect ratio.
We check the status code of the response to ensure the request was successful.
We parse the response and walk the returned parts, extracting the first part that contains inline image data (inlineData.data), which holds the base64-encoded image.
Finally, we pass the prompt and image data to our ImageManager's addImage() method, which stores it and returns the base64 string.
To understand why the extraction loop is written the way it is, here is a minimal example of the kind of response generateContent returns:
Notice that parts can contain a text part and an image part. That's why we loop through the parts and pick the first one that has inlineData.data, rather than assuming the image is always at a fixed position.
The method returns the base64-encoded image data, which can be used directly in web applications (for example, to display the image in an HTML <img> tag).
Generating images through an external API can fail for various reasons: network issues, API limits, invalid prompts, or server errors. To make our service robust, we've wrapped the API call in a try-catch block that catches any exceptions and throws a more informative RuntimeException.
Symfony provides a robust exception handling system that we can leverage to provide meaningful error messages and appropriate HTTP responses in our application. In a production environment, you might want to log these errors using Symfony's logging system:
PHP
use Psr\Log\LoggerInterface;// Add to constructorprivate $logger;public function __construct( PromptManager $promptManager, ImageManager $imageManager, HttpClientInterface $httpClient, ParameterBagInterface $params, LoggerInterface $logger) { // ... $this->logger = $logger;}public function generateImage(string $userInput): string{ try { // Request and process Gemini response } catch (\Exception $e) { // In the catch block $this->logger->error('Image generation failed: ' . $e->getMessage()); throw new \RuntimeException("Error generating image: " . $e->getMessage()); }}
Now, let's add one more method to our service to retrieve all previously generated images:
PHP
public function getImages(): array{ return $this->imageManager->getImages();}
This simple method delegates to our ImageManager's getImages() method, returning the complete list of stored images along with their associated prompts and IDs.
With these two methods, our ImageGeneratorService provides a complete interface for generating and retrieving images. The service integrates our previously built components (PromptManager and ImageManager) and connects them to the external Gemini image generation API.
Here's the complete implementation of our ImageGeneratorService:
Now that we've implemented our ImageGeneratorService, let's create a Symfony command to test it. This approach follows Symfony's best practices for testing services outside of a web context.
Create a new file in src/Command/GenerateImageCommand.php:
<?phpnamespace App\Command;use App\Service\ImageGeneratorService;use Symfony\Component\Console\Attribute\AsCommand;use Symfony\Component\Console\Command\Command;use Symfony\Component\Console\Input\InputArgument;use Symfony\Component\Console\Input\InputInterface;use Symfony\Component\Console\Output\OutputInterface;use Symfony\Component\Console\Style\SymfonyStyle;#[AsCommand( name: 'app:generate-image', description: 'Tests the ImageGeneration service with sample data',)]class GenerateImageCommand extends Command{ private $imageGeneratorService; public function __construct(ImageGeneratorService $imageGeneratorService) { $this->imageGeneratorService = $imageGeneratorService; parent::__construct(); } protected function configure() { $this ->setDescription('Generates an image using the ImageGeneratorService.') ->addArgument('prompt', InputArgument::REQUIRED, 'The prompt to generate an image from.'); } protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); $userInput = $input->getArgument('prompt'); $io->note('Generating image from prompt: ' . $userInput); try { $result = $this->imageGeneratorService->generateImage($userInput); $io->success('Image generated successfully!'); $trimmedResult = substr($result, 0, 30) . '...'; $io->text('Image: ' . $trimmedResult); // Display all stored images $io->section('All Stored Images:'); $images = $this->imageGeneratorService->getImages(); foreach ($images as $image) { $io->text('ID: ' . $image['id'] . ' | Prompt: ' . substr($image['prompt'], 0, 50) . '...'); } return Command::SUCCESS; } catch (\Exception $e) { $io->error('Error generating image: ' . $e->getMessage()); return Command::FAILURE; } }}
This command:
Accepts a prompt argument from the command line.
Calls our ImageGeneratorService to generate an image.
Displays the result and all stored images.
Handles errors gracefully.
To test this command, you would run:
Shell
php bin/console app:generate-image "Luxury Tech Conference 2025: Innovating the Future - April 10th, New York City"
When running this command with a valid API key, you would see output similar to:
Generating image from prompt: Luxury Tech Conference 2025: Innovating the Future - April 10th, New York City [OK] Image generated successfully! Image: iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB...All Stored Images:=================ID: 0 | Prompt: # ROLELead graphic designer# THEME...