Symfony is not just a framework – it is a set of independent PHP components you can use separately in any project. Magento 2 itself uses them (Console, Filesystem, Serializer). I show a few components that genuinely help in everyday PHP and Magento work: Console for writing CLI commands, Validator for validating data, and HttpClient for communicating with external APIs.
Symfony Console – CLI commands like in Magento
composer require symfony/console
<?php
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\ProgressBar;
class ImportProductsCommand extends Command
{
protected function configure(): void
{
$this
->setName('import:products')
->setDescription('Import products from a CSV file')
->addArgument('file', InputArgument::REQUIRED, 'Path to CSV file')
->addOption('dry-run', null, InputOption::VALUE_NONE, 'Validate only, no save')
->addOption('batch', 'b', InputOption::VALUE_OPTIONAL, 'Batch size', 100);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$file = $input->getArgument('file');
$dryRun = $input->getOption('dry-run');
$batch = (int) $input->getOption('batch');
if (!file_exists($file)) {
$output->writeln("File not found: {$file} ");
return Command::FAILURE;
}
$rows = array_slice(file($file), 1); // skip header
$total = count($rows);
$output->writeln("Importing {$total} products (batch: {$batch}) ");
if ($dryRun) {
$output->writeln('Dry-run mode - no database writes ');
}
$progressBar = new ProgressBar($output, $total);
$progressBar->start();
foreach (array_chunk($rows, $batch) as $chunk) {
foreach ($chunk as $row) {
// import logic
$progressBar->advance();
}
if (!$dryRun) {
// save batch to database
}
}
$progressBar->finish();
$output->writeln('');
$output->writeln('Import complete. ');
return Command::SUCCESS;
}
}
$app = new \Symfony\Component\Console\Application('Import Tool', '1.0.0');
$app->add(new ImportProductsCommand());
$app->run();
Symfony Validator – input data validation
composer require symfony/validator
<?php
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Validation;
$validator = Validation::createValidator();
$productData = [
'sku' => '', // error - required
'price' => -10.0, // error - must be >= 0
'email' => 'not-email', // error - invalid format
'name' => 'Test Product',
];
$constraints = new Assert\Collection([
'sku' => [new Assert\NotBlank(), new Assert\Length(['min' => 3, 'max' => 64])],
'price' => [new Assert\NotNull(), new Assert\GreaterThanOrEqual(0)],
'email' => [new Assert\Email()],
'name' => [new Assert\NotBlank(), new Assert\Length(['max' => 255])],
]);
$violations = $validator->validate($productData, $constraints);
if (count($violations) > 0) {
foreach ($violations as $violation) {
echo $violation->getPropertyPath() . ': ' . $violation->getMessage() . PHP_EOL;
}
}
Validation using PHP 8.0 attributes on DTOs:
<?php
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Validation;
class ProductImportDto
{
public function __construct(
#[Assert\NotBlank]
#[Assert\Length(min: 3, max: 64)]
public readonly string $sku,
#[Assert\NotBlank]
#[Assert\Length(max: 255)]
public readonly string $name,
#[Assert\GreaterThanOrEqual(0)]
public readonly float $price,
#[Assert\Email]
#[Assert\NotBlank]
public readonly string $contactEmail
) {}
}
$validator = Validation::createValidatorBuilder()
->enableAttributeMapping()
->getValidator();
$violations = $validator->validate(new ProductImportDto(
sku: 'MG-001', name: 'Test product', price: 29.99, contactEmail: 'not-an-email'
));
// [contactEmail]: This value is not a valid email address.
Symfony HttpClient – communicating with external APIs
composer require symfony/http-client
<?php
use Symfony\Component\HttpClient\HttpClient;
$client = HttpClient::create([
'base_uri' => 'https://api.example.com',
'headers' => ['Authorization' => 'Bearer ' . $token],
'timeout' => 10,
]);
// Synchronous request
$response = $client->request('GET', '/v1/products', ['query' => ['page' => 1, 'limit' => 100]]);
if ($response->getStatusCode() !== 200) {
throw new \RuntimeException('API error: ' . $response->getStatusCode());
}
$products = $response->toArray(); // automatic JSON decode
// Multiple parallel requests - Symfony executes them concurrently
$responses = [];
foreach ($productIds as $id) {
$responses[$id] = $client->request('GET', "/v1/products/{$id}");
}
// Data is fetched here - concurrently
foreach ($responses as $id => $response) {
$data = $response->toArray();
echo $data['name'] . PHP_EOL;
}
Summary
Symfony as a set of components is one of the best resources in the PHP ecosystem. You do not need to use the full framework to benefit from Console, Validator or HttpClient. In Magento 2 projects Symfony components appear naturally when writing CLI tools, validating data imported from external systems, and communicating with APIs – good places to start.
